/*

  This file is part of Kato Javascript Framework (http://kato.googlecode.com/)

  Copyright (c) 2007, Filip Dreger, Jan Urbański

  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:

      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
      * Redistributions in binary form must reproduce the above
        copyright notice, this list of conditions and the following
        disclaimer in the documentation and/or other materials provided
        with the distribution.
      * Neither the name of Kato Javascript Framework nor the names
        of its contributors may be used to endorse or promote products
        derived from this software without specific prior written
        permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

nag = true
nagme = function(msg){if (nag) nag = confirm(msg)}

Kato = {}

Kato.Active = {}
Kato.Request = {}
Kato.Cookies = {}
Kato.Config = {}
Kato.Uploads = {}
Kato.Models = {}
Kato.Providers = {}
Kato.Config.Markers = 'php/logs/markers'
Kato.Config.BaseUrl = '../kato'
Kato.Config.LogoutUrl = '?logout=1'
Kato.Config.KeyColumnRegex = new RegExp("^id_");
Kato.Config.GetRelations = true
Kato.Config.Flash = false;
Kato.Flash = {}
Kato.Utils = {}

DumperMaxDepth = 3

Kato.Message = function(msg) {
	alert(msg)
}

Kato.Error = function(msg) {
	if (typeof msg == 'string') {
		if(msg.indexOf('cannot connect')==0){
			if(Kato.Config.LogoutUrl) window.location.href=Kato.Config.LogoutUrl
			return
		}
		alert(msg)
	}
	else {
		alert(Dumper(msg))
	}
}
Kato.Log = function(){}
Kato.Translations = {
	yes:			'tak',
	no:			'nie',
	new_item:		'wyczyść',
	save_new:		'zapisz nowy',
	clear:			'wyczyść',
	edit:			'zapisz',
	add:			'dodaj',
	save:			'nagraj',
	remove:			'usuń',
	next_page:		'następna',
	prev_page:		'poprzednia',
	page:			'strona',
	new_combo_item:         '[nowy]',
	confirm_remove:	'operacja usunięcia jest nieodwracalna. Czy na pewno kontyunować?',
	confirm_new_item:'wszystkie zmiany wprowadzone od ostatniego zapisu zostaną stracone. Czy na pewno kontynuować?',

	error_not_null:		'wartość jest wymagana',
	error_password:		'trzeba podać hasło przy tworzeniu nowego obiektu',
	error_integer:		'wymagana jest liczba całkowita',
	error_numeric:		'wymagana jest liczba',
	error_timestamp:	'wymagane jest podanie czasu w formacie RRRR-MM-DD GG:MM',
	error_date:		'wymagane jest podanie czasu w formacie RRRR-MM-DD',
	error_time:		'wymagane jest podanie godziny w formacie GG:MM(:SS)',
	save_last_item_first:
					'najpierw należy zapisać dane z poprzednio redagowanego pola'
}
Kato.Templates = {}

/*
 * Kato.TemplateRegexes is needed when you have an editable grid, that has a column rendered by Kato.Renderers.get_renderer_combo_cell or similar.
 * If you need the injected Combos to have some attribute (min_search_chars comes to mind), you can't use Kato.Templates, because you don't know the injected
 * Combos ids.
 *
 * This does not prove that Kato.TemplateRegexes is needed, of course. This proves that Kato.Templates is broken, but Kato.TemplateRegexes is
 * introduced as a temporary solution and should go away someday.
 *
 * Sigh... this comment will selfdestruct in four years, so *please* don't make it last that long.
 */
Kato.TemplateRegexes = {}

Kato.Cookies.options = {}
Kato.Cookies.options.expires = '360000000000'
Kato.Cookies.options.path = ''
Kato.Cookies.options.domain = ''
Kato.Cookies.options.secure = ''
Kato.Cookies.keys = $A()

Kato.Cookies.put = function(key, value) {
	str = key + '=' + Object.toJSON(value)
	if (Kato.Cookies.options.expires) {
		date = new Date()
		date.setTime(date.getTime() + parseInt(Kato.Cookies.options.expires))
		str += '; expires=' + date.toGMTString()
	}
	if (Kato.Cookies.options.path) {
		str += '; path=' + Kato.Cookies.options.path
	}
	if (Kato.Cookies.options.domain) {
		str += '; domain=' + Kato.Cookies.options.domain
	}
	if (Kato.Cookies.options.secure) {
		str += '; secure'
	}
	Kato.Cookies.keys.push(key)
	document.cookie = str
}

Kato.Cookies.get = function(key) {
	value = document.cookie.match(key + '=([^;]*)(;|$)')
	return value ? value[1].evalJSON() : undefined
}

Kato.Cookies.remove = function(key) {
	date = new Date()
	date.setTime(date.getTime() - 3600)
	str = key + '=; expires=' + date.toGMTString()
	if (Kato.Cookies.options.path) {
		str += '; path=' + Kato.Cookies.options.path
	}
	if (Kato.Cookies.options.domain) {
		str += '; domain=' + Kato.Cookies.options.domain
	}
	if (Kato.Cookies.options.secure) {
		str += '; secure'
	}
	document.cookie = str
}


Kato.Cookies.clear = function() {
	Kato.Cookies.keys.each(function(key) {
		Kato.Cookies.remove(key)
	})
}

Kato.Utils.date_from_string = function(date) {
		return new Date(parseInt(date.substr(0,4)),parseInt(date.substr(5,2).replace(/^0/,''))-1,parseInt(date.substr(8,2).replace(/^0/,'')))
}

Kato.Utils.string_from_timestamp = function (date){
	var hours = date.getHours()
	var minutes = date.getMinutes()
	if (minutes<10) minutes = '0' + minutes
	if (hours<10) hours = '0' + hours
	return Kato.Utils.string_from_date(date)
			+ ' '
			+ hours
			+ ':'
			+ minutes
}

Kato.Utils.timestamp_from_string = function(date){
	try {
		var year = parseInt(date.substr(0,4))
		var month = parseInt(date.substr(5,2).replace(/^0/,''))-1
		var day = parseInt(date.substr(8,2).replace(/^0/,''))
		var hours = parseInt(date.substr(11,2).replace(/^0/,''))
		var minutes = parseInt(date.substr(14,2).replace(/^0/,''))
		if (!hours) hours = 0;
		if (!minutes) minutes = 0;
	} catch (e) {
		return null
	}
	return new Date(year,month,day, hours, minutes, 0)
}

Kato.Utils.this_month_ge = function() {
	var mydate = new Date();
	mydate.setDate(1)
	return Kato.Utils.string_from_date(mydate)
}

Kato.Utils.this_month_lt = function() {
	var mydate = new Date()
	mydate.setMonth(mydate.getMonth()+1,1)
	return Kato.Utils.string_from_date(mydate)
}

Kato.Utils.string_from_date = function(date) {
		return ''
			+ date.getFullYear()
			+ '-'
			+ ((date.getMonth()<9) ? '0' : '')
			+ (date.getMonth()+1)
			+ '-'
			+ ((date.getDate()<10) ? '0' : '')
			+ date.getDate()
}

Kato.Utils.column_to_desc = function(column) {
	var desc = ''
	if (column['not null'])
		desc += '<span class="required_field">*</span> '
	desc += Kato.Translate(column['column'])
	return desc
}

Kato.Utils.idle = {
	element: undefined,
	init:function(element){
		if(element==undefined){
			element = document.createElement('div');
			element.id = 'kato_flash_idle_container'
			document.body.insertBefore(element,document.body.firstChild)
		}
		this.element = element
		if(!Kato.Config.Flash){
			element.innerHTML='Wczytywanie...'
		} else {
			element.style.width = '64px';
			var so = new SWFObject(Kato.Config.BaseUrl + '/flash/idle.swf', "kato_flash_idle", "32", "32", "9", "#FFF");
	  		so.addParam("wmode", "transparent");
	  		so.write(this.element.id)
		}
	},

	busy:function(){
		//if(!Kato.Config.Flash)return;
		if(this.element==undefined){
			this.init()
		}
//		document.body.style.cursor='wait'
		this.element.style.display = ''
		//alert('busy')
	},

	idle:function(){
		//if(!Kato.Config.Flash)return;
		if(this.element==undefined){
			this.init()
		}
//		document.body.style.cursor=''
		this.element.style.display = 'none'
		//alert('idle')
	}
}


Kato.Utils.MP3 = {
	init:function(url){
		if(Kato.Flash.mp3) return;
		if(!SWFObject)Kato.Error('MP3 functionality in Kato requires the SWFObject Javascript library (free, easy to find, go get it)')
		var so = new SWFObject(Kato.Config.BaseUrl + '/flash/mp3.swf', "kato_flash_thingy", "0", "0", "9", "#F00");
		so.addParam("allowScriptAccess", "always");
		var element = document.createElement('div');
		element.id = 'kato_flash_thingy_container'
		document.body.insertBefore(element,document.body.firstChild)
		so.write("kato_flash_thingy_container");
		Kato.Flash.mp3 = document.getElementById('kato_flash_thingy')
	},

	load:function(url){
		Kato.Flash.mp3.load(url)
	},

	play:function(){
		Kato.Flash.mp3.play()
	},
	stop:function(){
		Kato.Flash.mp3.stop()
	},
	set_volume:function(vol){
		Kato.Flash.mp3.set_volume(vol)
	}

}

Kato.Updater = function(){
	for (name in Kato.Active){
		if(Kato.Active[name].refresh){
			Kato.Active[name].refresh()
		}
	}
	//setTimeout('Kato.Updater()',10000)
}

Kato.Months = [
	'Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec','Lipiec','Sierpień','Wrzesień',
	'Październik','Listopad','Grudzień'
]

Kato.Weekdays = [
	'Pon','Wt','Śr','Czw','Pt','So','Nie'
]

/*Kato.Months = [
	'January','February','March','April','May','June','July','August','September','October','November','December'
]*/

//Kato.Log = DumperAlert

Kato.Regex = {
	escapeRegex: new RegExp(
		'(\\' + [ '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '/' ].join('|\\') + ')',
		'g'
	),

	escape: function(val) {
		return val.replace(Kato.Regex.escapeRegex, '\\$1')
	}
}

Kato.Translate = function(text){
	if(Kato.Translations[text]){
		text = Kato.Translations[text]
	} else {
		text = text.replace(/_/g,' ')
	}
	return text
}

Kato.Store = {}

Kato.Providers.HttpProvider = {
	current_version:function(view){
		var url = Kato.Config.BaseUrl + '/' + Kato.Config.Markers + '/' + view
		var request = new Ajax.Request (
			url,
			{
				asynchronous: false
			}
		)

		if (request.transport.status==200){
			return request.transport.responseText;
		} else {
			return undefined;
		}
	},

	get_primary_keys:function(){
		var url = Kato.Config.BaseUrl + '/php/?view=KatoPrimaryKeys';
		var request = new Ajax.Request(url,{asynchronous:false})
		results = eval ('(' + request.transport.responseText + ')')
		return results
	},

	do_operations:function(view, params){
			var url = Kato.Config.BaseUrl + '/php/?view=' + view;
			if(params.filename_column != undefined){
				url += '&filename_column=' + params.filename_column
			}
			Kato.Utils.idle.busy();
			var request = new Ajax.Request(
				url,
				{
					method:'post',
					parameters: {operations: params.operations.toJSON()},
					asynchronous: false
				}
			)

			if(request.transport.responseText){
				var data = eval('('+request.transport.responseText+')');
				if (data.error){
					Kato.Error(data.error)
					throw 'invalid data'
				}
			}
			Kato.Utils.idle.idle();
	},

	get_data:function(view, params){
		Kato.Utils.idle.busy();
		var url = Kato.Config.BaseUrl + '/php/?view=' + view
		$H(params).each(function(pair) {
			url += '&search[' + pair[0] + ']=' + encodeURIComponent(pair[1])
		})

		var request = new Ajax.Request(
			url,
			{ asynchronous:false }
		)
		Kato.Utils.idle.idle();

		var data = eval('('+request.transport.responseText+')');
		if (data.error){
			Kato.Error(data.error)
			throw 'invalid data'
		} else {
			return data
		}
	}
}

Kato.Config.Provider = Kato.Providers.HttpProvider

Kato.Providers.JSONRPCProvider = {

	rpc_id: 0,

	current_version:function(view){
		return undefined;
	},

	get_primary_keys:function(){
		var url = Kato.Config.BaseUrl;

		Kato.Utils.idle.busy();

		var request = new Ajax.Request(
			url,
			{
				method:				'post',
				asynchronous:		false,
				postBody:			Object.toJSON({
					method: 'get_primary_keys',
					params: [
						null
					],
					id:		this.rpc_id++
				})
			}
		)

		if(request.transport.responseText){
			var data = eval('('+request.transport.responseText+')');
			if (data.error){
				Kato.Error(data.error)
				Kato.Utils.idle.idle();
				throw 'invalid data'
			}
			return data.result.primary_keys
		}
	},

	do_operations:function(view, params, flags){
		if (flags === undefined) { flags = {} }
		var url = Kato.Config.BaseUrl;
		Kato.Utils.idle.busy();

		var request = new Ajax.Request(
			url,
			{
				method:				'post',
				asynchronous:		false,
				postBody:			Object.toJSON({
					method: 'do_operations',
					params: [
						view,
						params.operations,
						flags
					],
					id:		this.rpc_id++
				})
			}
		)
		Kato.Utils.idle.idle();
		var data = eval('('+request.transport.responseText+')');
		if(request.transport.responseText){
			if (data.error){
				Kato.Utils.idle.idle()
				throw data.error
			}
		return data.result
		}
	},

	get_postBody:function(view, params, flags){
		if (flags === undefined) { flags = {} };
		var o = {
			method: 'get_data',
			params: [
				view,
				params,
				flags
			],
			id:		this.rpc_id++
		}
		return o
	},

	get_data:function(view, params){
		Kato.Utils.idle.busy();
		var url = Kato.Config.BaseUrl

		var request = new Ajax.Request(
			url,
			{
				method:				'post',
				asynchronous:		false,
				postBody:			Object.toJSON(this.get_postBody(view,params))
			}
		)

		Kato.Utils.idle.idle();

		var data = eval('('+request.transport.responseText+')');
		if (data.error){
			Kato.Error(data.error)
			throw 'invalid data'
		} else {
			return data.result
		}
	}
}

if (window.parentSandboxBridge) {
	Kato.Providers.AIRProvider = {
	        current_version: window.parentSandboxBridge.current_version,
	        get_primary_keys: window.parentSandboxBridge.get_primary_keys,
	        do_operations: window.parentSandboxBridge.do_operations,
	        get_data: window.parentSandboxBridge.get_data
	};
}

Kato.Providers.LocalProvider = {
	get_data:function(view, params){
		return Kato.Models[view]
	},

	do_operations:function(){},

	current_version:function(){}
}





Kato.Helpers = {
	inject_dropdown:function(id_where,id_parent,bind_col,bind_row){
		var params = {}
		params.id = ($(id_where).id?$(id_where).id:'') + id_parent + 'Kdropdown' + bind_col + '_' + bind_row
		params.model = Kato.PrimaryKeys[Kato.Active[id_parent].columns[bind_col].column]
		if(!Kato.PrimaryKeys[Kato.Active[id_parent].columns[bind_col].column]){
			Kato.Error('wrong view for key ' + Kato.Active[id_parent].columns[bind_col].column)
		}
		params.type = 'Kato.Dropdown'
		params.cell_binding= "'" + id_parent + "'," + bind_col + "," + bind_row
		Kato.BuildFromScratch(params,id_where)
		Kato.Active[params.id].value = Kato.Active[id_parent].rows[bind_row][bind_col]
		Kato.Active[params.id].render()
		$(id_where).onclick = ''
		Kato.Active[params.id].change_handlers.push(
			function(){
				Kato.Active[params.id].model.remove_listener(params.id)
				Kato.Active[id_parent].send_data()
				delete Kato.Active[params.id]
			}
		)
	},


	inject_combo:function(id_where,id_parent,bind_col,bind_row){
		var params = {}
		params.id = ($(id_where).id?$(id_where).id:'') + id_parent + 'Kcombo' + bind_col + '_' + bind_row
		params.model = Kato.PrimaryKeys[Kato.Active[id_parent].columns[bind_col].column]
		if(!Kato.PrimaryKeys[Kato.Active[id_parent].columns[bind_col].column]){
			Kato.Error('wrong view for key ' + Kato.Active[id_parent].columns[bind_col].column)
		}
		params.type = 'Kato.Combo'
		params.cell_binding= "'" + id_parent + "'," + bind_col + "," + bind_row
		Kato.BuildFromScratch(params,id_where)
		Kato.Active[params.id].value = Kato.Active[id_parent].rows[bind_row][bind_col]
		Kato.Active[params.id].render()
		$(id_where).onclick = ''
		Kato.Active[params.id].change_handlers.push(
			function(){
				Kato.Active[params.id].model.remove_listener(params.id)
				Kato.Active[id_parent].send_data()
				delete Kato.Active[params.id]
			}
		)
	}

}

Kato.Resize = function(element,mouse_x){
	element = $(element)
	element.original_width = Element.getWidth(element)
	element.original_pointerX = mouse_x
	Kato.Store.resize_observer = function(event){
		document.body.style.cursor = 'e-resize'
		element.style.width = (element.original_width + Event.pointerX(event) - element.original_pointerX) + 'px'
	}
	Kato.Store.stop_resizing = function(event){
		document.body.style.cursor = ''
		Event.stopObserving(document.body,'mousemove',Kato.Store.resize_observer)
		Event.stopObserving(document.body,'mouseup',Kato.Store.stop_resizing)
	}
	Event.observe(document.body,'mousemove',Kato.Store.resize_observer)
	Event.observe(document.body,'mouseup',Kato.Store.stop_resizing)
}



Kato.GetModel = function(viewname){
	var model;
	if (!Kato.Active[viewname]){
		model = new Kato.Model(viewname)
		Kato.Active[viewname] = model

		if (Kato.Models[viewname]){
			model.provider = Kato.Providers.LocalProvider
		} else {
			model.provider = Kato.Config.Provider
		}

	}
	return Kato.Active[viewname]
}

Kato.Upload = function(upload_id){
	var upload = Kato.Uploads[upload_id]
	upload.bind_to.model.filename_column = upload.column_index
	upload.bind_to.set_cell(upload.row_index, upload.column_index, upload.filename)
	upload.bind_to.send_data()
}

Kato.Extract = function(element, attribute, container, default_value) {
	if (default_value === undefined) default_value = ''
	var content = element.hasAttribute(attribute) ? element.getAttribute(attribute) : default_value;
	var code
	if (container.constructor == Object) {
		code = '({' + content + '})'
	}
	else if (container.constructor == Array) {
		code = '([' + content + '])'
	}
	else if (container.constructor == String) {
		code = '(\'' + content + '\')'
	}
	else if (container.constructor == Boolean) {
		code = '(' + content + ')'
	}
	//var code = (container.constructor == Object ? ('({' + content + '})') : ('([' + content + '])'))
	return eval(code)
}


Kato.Renderers = {
	get_renderer_show_column:function(column_to_show){
		return function(row,index,id_row_index){
			return '<td>' + row[column_to_show] + '</td>'
		}
	},
	get_renderer_dropdown_cell:function(column_with_pk){
		return function(row,index,id,row_index){
			return Kato.Renderers.build_dropdown_cell.bind(this)(row,index,id,row_index,column_with_pk)
		}
	},
	get_renderer_combo_cell:function(column_with_pk){
		return function(row,index,id,row_index){
			return Kato.Renderers.build_combo_cell.bind(this)(row,index,id,row_index,column_with_pk)
		}
	},
	get_form_renderer:function(builder) {
		var fn = function(row,index,id,row_index) {
			var o = ''
			o += '<tr><th style="width: 30%">' + Kato.Utils.column_to_desc(this.columns[index]) + '</th>'
			o += builder.bind(this)(row,index,id,row_index)
			o += '</tr>'
			return o
		};
		return fn;
	},

	build_bool_cell:function(row,index,id,row_index){
		var o = '';
		var val = Kato.Active[id].rows[row_index][index]
		val = (val==true)?Kato.Translate('yes'):Kato.Translate('no')
		o += '<td>' + val + '</td>'
		return o
	},


	build_delete_cell:function(row, index, id, row_index, careful){
		var o = '<td><input type="button" class="kato_button" value="X" onclick="';
                if (careful == true) {
                     o += 'if(confirm(Kato.Translate(\'confirm_remove\')))'
                }
		o += 'Kato.Active.' + id + '.delete_from(' + row_index + ')" /></td>'
		return o
	},

        build_careful_delete_cell:function(row, index, id, row_index){
		var o = Kato.Renderers.build_delete_cell(row,index,id,row_index,true);
		return o
	},

	build_editable_cell:function(row,index,id,row_index){
			var o = '';
			o += "<td>";
			val = row[index]==null? '&nbsp;' : row[index]
			o += "<div class='form_editable' onclick='$(this).hide(); $(this.nextSibling).show(); $(this.nextSibling).getElementsByTagName(\"input\")[0].focus()'>" + val + "</div><div style='display: none'>"
			o += Kato.Renderers.build_bound_input.bind(this)(row, index, id, row_index)
			o += "</div></td>\n"
			return o
	},

	build_dropdown_cell:function(row,index,id,row_index,column_with_pk){
			if(column_with_pk===undefined)column_with_pk=index
			if(typeof(column_with_pk)!='number') column_with_pk = this.model.to_index(column_with_pk)
			var o = ''
			o += "<td onclick='Kato.Helpers.inject_dropdown(this,\"" + id + "\", " + column_with_pk + "," + row_index + ")'>"
			o += (row[index] === null) ? '---' : Kato.Utils.escape_all(row[index]) + "</td>"
			return o
	},

	build_combo_cell:function(row,index,id,row_index,column_with_pk){
			if(column_with_pk===undefined)column_with_pk=index
			if(typeof(column_with_pk)!='number') column_with_pk = this.model.to_index(column_with_pk)
			var o = ''
			o += "<td onclick='Kato.Helpers.inject_combo(this,\"" + id + "\", " + column_with_pk + "," + row_index + ")'>"
			o += row[index] + "</td>"
			return o
	},

	build_date_cell:function(row,index,id,row_index){
		Kato.Utils.string_from_date(new Date())
		var o = '';
		var val = Kato.Active[id].rows[row_index][index]
		val = ((val!=null)?val:'');
		o += '<td>'
		o += '<input style="width: 12em" onkeyup="Kato.Active.' + id + '.set_cell(' + row_index + ',' + index + ',this.value)" class="text" type="text" value="' + val + '" />'
		o += "<input type='button' class='kato_button' value='dziś' onclick='this.previousSibling.value=Kato.Utils.string_from_date(new Date()); this.previousSibling.onkeyup()'>";
		o += '</td>'
		return o
	},

	build_timestamp_cell:function(row,index,id,row_index){
		Kato.Utils.string_from_date(new Date())
		var o = '';
		var val = Kato.Active[id].rows[row_index][index]
		val = ((val!=null)?val:'')
		o += '<td>'
		o += '<input style="width: 12em" onkeyup="Kato.Active.' + id + '.set_cell(' + row_index + ',' + index + ',this.value)" class="text" type="text" value="' + val + '" />'
		o += "<input type='button' class='kato_button' value='teraz' onclick='this.previousSibling.value=Kato.Utils.string_from_date(new Date()) + \" \" + new Date().getHours() + \":\" + new Date().getMinutes(); this.previousSibling.onkeyup()'>";
		o += '</td>'
		return o
	},

	build_date_form_cell:function(row,index,id,row_index){
		var o = ''
		o += '<tr><th style="width: 30%">' + Kato.Utils.column_to_desc(this.columns[index]) + '</th>'
		o += Kato.Renderers.build_date_cell(row,index,id,row_index)
		o += '</tr>'
		return o
	},

	build_timestamp_form_cell:function(row,index,id,row_index){
		var o = ''
		o += '<tr><th style="width: 30%">' + Kato.Utils.column_to_desc(this.columns[index]) + '</th>'
		o += Kato.Renderers.build_timestamp_cell(row,index,id,row_index)
		o += '</tr>'
		return o
	},

	build_checkbox_cell:function(row,index,id,row_index){
		var val = row[index]
		if(!(val==null || val=='f' || val==''))val='t'
		var checked = (val=='t')?"checked='checked'":""
		var o = ''
		o += '<td>'
		o += '<input type="checkbox" onchange="Kato.Active.' + id + '.set_cell(' + row_index + ',' + index + ',this.checked?\'t\':\'f\')" class="text" ' + checked + '>'
		o += '</td>'
		return o
	},

	build_booldropdown_cell:function(row,index,id,row_index){
		/* display boolean and allows to set it */
		var val = row[index]
		if(!(val==null || val=='f' || val==false))val='t'
		if (val=='t') {
			tsel = 'selected'
			fsel = ''
		} else {
			tsel = ''
			fsel = 'selected'
		}
		var valt = (val=='t')?Kato.Translate('yes'):Kato.Translate('no')
		var o = ''
		o += '<td>'
		o += '<span onclick="this.style.display = \'none\'; this.nextSibling.style.display = \'\'">'+valt+'</span>'
		o += '<select '
		o +=	'style="display:none;" '
		o +=	'onchange="val = (this.options[this.selectedIndex].value==\'t\'); '
		o +=		'Kato.Active.' + id + '.set_cell(' + row_index + ',' + index + ',val); '
		o += 		'Kato.Active.' + id + '.send_data(); '
		o +=		'this.style.display = \'\'; this.previousSibling.style.display = \'none\''
		o += '">'
		o += '<option ' + tsel + ' value="t">'+Kato.Translate('yes')+'</option>'
		o += '<option ' + fsel + ' value="f">'+Kato.Translate('no')+'</option>'
		o += '</select>'
		o += '</td>'
		return o
	},

	build_booldropdown_form_cell:function(row,index,id,row_index){
		var val = row[index]
		if(!(val==null || val=='f' || val==false))val='t'
		if (val=='t') {
			tsel = 'selected'
			fsel = ''
		} else {
			tsel = ''
			fsel = 'selected'
		}
		var o = ''
		o += '<tr><th style="width: 30%">' + Kato.Utils.column_to_desc(this.columns[index]) + '</th>'
		o += '<td>'
		o += '<select '
		o +=	'onchange="val = (this.options[this.selectedIndex].value==\'t\'); '
		o +=		'Kato.Active.' + id + '.set_cell(' + row_index + ',' + index + ',val); '
		o += '">'
		o += '<option ' + tsel + ' value="t">'+Kato.Translate('yes')+'</option>'
		o += '<option ' + fsel + ' value="f">'+Kato.Translate('no')+'</option>'
		o += '</select>'
		o += '</td>'
		o += '</tr>'
		return o
	},

	build_checkbox_form_cell:function(row,index,id,row_index){
		var o = ''
		o += '<tr><th style="width: 30%">' + Kato.Utils.column_to_desc(this.columns[index]) + '</th>'
		o += Kato.Renderers.build_checkbox_cell(row,index,id,row_index)
		o += '</tr>'
		return o
	},

	build_file_upload:function(row,index,id,row_index){
		var o = ''
		if (this.uploadcounter === undefined) {
			this.uploadcounter = 0;
			this.uploads = new Array();
		}
		this.uploads[this.uploadcounter] = false;
		var base_name = this.id + 'KuploadK' + this.uploadcounter;
		var form_id = this.id + 'KuploadformK' + this.uploadcounter;
		Kato.Uploads[base_name] = {bind_to:this, 'row_index':row_index, 'column_index':index}
		o += "<form id='" + form_id + "' target='" + base_name + "' action='" + Kato.Config.BaseUrl + "' method='post' enctype='multipart/form-data'>"
		o += "<input type='file' name='file' onchange='Kato.Active." + this.id + ".uploads[" + this.uploadcounter + "] = true;' />"
		o += "</form><iframe style='display: none' name='" + base_name + "' id='" + base_name + "></iframe>"
		this.uploadcounter++;
		return '<td>' + o + '</td>'
	},

	build_file_download:function(row,index,id,row_index){
		return '<td><a href="/upload/' + row[index] + '">Pobierz</a></td>';
	},

	build_bound_input:function(row,index,id,row_index){
		var o = '';
		var val = Kato.Active[id].rows[row_index][index]
		val = ((val!=null)?val:'')
		o += '<input onclick="Event.stop(event)" onkeyup="if(event.keyCode==Event.KEY_RETURN){Kato.Active.' + id + '.set_cell(' + row_index + ',' + index + ',this.value); Kato.Active.' + id + '.send_data(); return};Kato.Active.' + id + '.set_cell(' + row_index + ',' + index + ',this.value)" class="text" type="text" value="' + val + '">'
		return o
	},

	build_textarea_form_field:function(row,index,id,row_index){
		var o = ''
		o += '<tr><th style="width: 30%">' + Kato.Utils.column_to_desc(this.columns[index]) + '</th>'
		o += Kato.Renderers.build_textarea_cell.bind(this)(row,index,id,row_index)
		o += '</tr>'
		return o
	},

	build_textarea_cell:function(row,index,id,row_index){
		var o = '';
		var val = Kato.Active[id].rows[row_index][index]
		val = ((val!=null)?val:'')
		o += '<td><textarea onclick="Event.stop(event)" onkeyup="Kato.Active.' + id + '.set_cell(' + row_index + ',' + index + ',this.value)" class="text" type="text">' + Kato.Utils.escape_tags(val) + '</textarea></td>'
		return o
	},

	build_order_switcher:function(row, index, id, row_index) {
		// dając buttony zamiast obrazków można zaoszczędzić ~2s czasu ładowania strony...
		var o = '<td style="text-align: center">';
		o += '<img class="orderby_arrow" src="' + Kato.Config.BaseUrl + '/web/img/up-arrow.png" style="margin: 0px"'
		//o += '<input type="button" class"kato_button" value="w górę" '
		if (row_index != 0) {
			o += 'onclick="var tmp = Kato.Active.' + id + '.rows[' + (row_index - 1) + '][' + index + ']; Kato.Active.' + id + '.set_cell(' + (row_index - 1) + ',' + index + ',\'' + row[index] + '\'); Kato.Active.' + id + '.set_cell(' + row_index + ',' + index + ',tmp); Kato.Active.' + id + '.send_data()"'
		}
		else {
			o += 'onclick="Event.stop(event)"'
		}
		o += '>'
		o += '<img class="orderby_arrow" src="' + Kato.Config.BaseUrl + '/web/img/down-arrow.png" style="margin: 0px"'
		//o += '<input type="button" class"kato_button" value="w dół" '
		if (row_index != (Kato.Active[id].rows.length - 1)) {
			o += 'onclick="var tmp = Kato.Active.' + id + '.rows[' + (row_index + 1) + '][' + index + ']; Kato.Active.' + id + '.set_cell(' + (row_index + 1) + ',' + index + ',\'' + row[index] + '\'); Kato.Active.' + id + '.set_cell(' + row_index + ',' + index + ',tmp); Kato.Active.' + id + '.send_data()"'

		}
		else {
			o += 'onclick="Event.stop(event)"'
		}
		o += '>'
		o += '</td>'
		return o
	},

	DefaultRenderers: {
		timestamp: function(row, index, id, row_index) {
			var o = '';
			var val = row[index];
			if (val !== null)
				val = val.replace(/\.\d+$/, '');
			o += '	<td>';
			o += val === null || val === '' ? '&nbsp;' : val;
			o += "</td>\n"
			return o
		}
	}
}



Kato.Mutators = {
	strip:function(val) {
		if(typeof(val)=='string')
			return val.strip()
		return val
	},

	strip_whitespace:function(val) {
		if (!val.replace) return val
		return val.replace(/\s*/g, '')
	},

	commas_to_points:function(val) {
		return val.replace(/,/g, '.')
	},

	parse_timestamp:function(val) {
		return val //FIXME potrzebny potężny parser
	},

	parse_date:function(val) {
		return val //FIXME potrzebny potężny parser
	},

	set_default:function(def) {
		return function(val) { return val == '' ? def : val }
	}
}

Kato.Validators = {
	not_null:function(val) {
		return val != '' ? null : Kato.Translate('error_not_null')
	},

	password:function(val, mutated, i, form) {
  	        if (form.insert_mode)
			return val != '' ? null : Kato.Translate('error_password');
		return null;
	},

	integer:function(val) {
		if (!val.match) return null
		return val.match(/^(\s*[+-]?[0-9]+\s*)?$/) ? null : Kato.Translate('error_integer')
	},

	numeric:function(val) {
		if (!val.match) return null
		return val.match(/^(\s*[+-]?[0-9]*\.?[0-9]+\s*)?$/) ? null : Kato.Translate('error_numeric')
	},

	// poniżej musi przechodzić: "2007-11-12 12:49:26.678529"
	timestamp:function(val) {
		return val.match(/^(\d\d\d\d-\d\d-\d\d\s*(\d{1,2}(:\d{1,2}(:\d\d(.\d*)?)?)?)?)?$/) ? null : Kato.Translate('error_timestamp')
		//return val.match(/^(\s*([1-9]|([0-1][0-9])|(2[0-4])):[0-5]?[0-9]?(:[0-5]?[0-9])?\s*)?$/) ? null : Kato.Translate('error_timestamp')
		//return val == '' ? null : !isNaN(Date.parse(val)) ? null : Kato.Translate('error_timestamp')
		return null //FIXME potrzebny potężny regex
	},

	date:function(val) {
		// wyłapuje większość błędów, lepsze niż nic...
		return val.match(/^(\d\d\d\d-(\d)?\d-(\d)?\d)?$/) ? null : Kato.Translate('error_date')
		return null //FIXME potrzebny potężny regex
	},

	time:function(val) {
		return val.match(/^(\d\d:\d\d(:\d\d)?)?$/) ? null : Kato.Translate('error_time')
	},

	text:function(val, row) {
		return null
	}
}

Kato.Active.dummy_model = {
	select:function(){
		return {
			data:[],
			columns:[],
			rows:[]
		}

	},
	get_empty_row:function(){
		return []
	},
	hash_from_row:function(row){
		return {}
	},
	col_name_to_index:function(){},
	count:function(){},
	refresh:function(){},
	add_listener:function(){}
}

Kato.StaticModel = Class.create();
Kato.StaticModel.prototype = Object.extend ({},{
	data: [],
	columns: [],
	rows: [],

	select:function(){
		return {
			data:this.data,
			columns:this.columns,
			rows:this.rows
		}

	},
	initialize:function(){},
	col_name_to_index:function(){},
	count:function(){},
	refresh:function(){},
	add_listener:function(){},
	hash_from_row:function(){}
})

Kato.FlushOpsHook = function(result, success) {
	if (success) {
		var d = new Date()
		var message = d.getHours()+':'+d.getMinutes()+':'+d.getSeconds()+"\n"
		message += ((result != null) && (result.message !== undefined)) ? result.message : Kato.Translate('success')
		// alert(message)
	}
}

Kato.Model = Class.create();
Kato.Model.prototype = Object.extend ({},{

	after_select:		false,

	data:			new Array(),
	operations:		new Array(),
	listeners:		new Array(),
	pending_validators:	undefined,
	pending_mutators:	undefined,
	validators:		undefined,
	mutators:		undefined,
	current_filters:	undefined,
	filename_column:	undefined,
	view:				undefined,
	refresh_view:		undefined,
	rows_to_validate:	undefined,
	force_insert:           false,

	initialize:function(view){
		this.view = view
		this.refresh_view = view
		this.state = 'empty'
		this.current_filters = new Object()
		this.flush_mode = 'always'
		this.listeners = new Array()
		this.operations = new Array()
		this.pending_validators = new Array()
		this.pending_mutators = new Array()
		this.validators = new Array()
		this.mutators = new Array()
		this.rows_to_validate = new Array()
	},

	initialize_validators:function(){
		this.validators[0] = []
		for (var i = 1; i < this.data.columns.length; i++) {
			this.validators[i] = []
			if (this.data.columns[i]['not null'] && !this.data.columns[i]['has default']) {
				this.validators[i].push(Kato.Validators.not_null)
			}
			if (this.data.columns[i].column.match(/password|haslo/)) {
				this.validators[i].push(Kato.Validators.password)
			}

			switch (this.data.columns[i]['type']) {

			case 'int4':
				this.validators[i].push(Kato.Validators.integer)
				break

			case 'numeric':
				this.validators[i].push(Kato.Validators.numeric)
				break

			case 'text':
			case 'varchar':
				this.validators[i].push(Kato.Validators.text)
				break

			case 'date':
				this.validators[i].push(Kato.Validators.date)
				break

			case 'timestamp':
			case 'timestamptz':
				this.validators[i].push(Kato.Validators.timestamp)
				break

			case 'time':
				this.validators[i].push(Kato.Validators.time)
				break

			}
		}
		this.pending_validators.each((function(elem) {
			this.validators[this.to_index(elem[0])].push(elem[1])
		}).bind(this))
	},

	initialize_mutators:function(){
		this.mutators[0] = []
		for (var i = 1; i < this.data.columns.length; i++) {
			this.mutators[i] = []
			this.mutators[i].push(Kato.Mutators.strip)
			switch (this.data.columns[i]['type']) {

			case 'numeric':
				this.mutators[i].push(Kato.Mutators.commas_to_points)
				/* fallthrough */
			case 'int4':
				this.mutators[i].push(Kato.Mutators.strip_whitespace)
				break

			case 'timestamp':
			case 'timestamptz':
				this.mutators[i].push(Kato.Mutators.parse_timestamp)
				break

			case 'date':
				this.mutators[i].push(Kato.Mutators.parse_date)
				break
			}
		}
		this.pending_mutators.each((function(elem) {
			this.mutators[this.to_index(elem[0])].push(elem[1])
		}).bind(this))
	},

	add_listener:function(listener){
		this.listeners.push(listener)
	},

	remove_listener:function(listener){
		for (var v=0; v < this.listeners.length; v++){
			if(this.listeners[v]==listener){
				this.listeners.splice(v,1);
				return;
			}
		}
	},

	add_validator:function(index, validator){
		if (this.after_select) {
			this.validators[this.to_index(index)].push(validator)
		}
		else {
			this.pending_validators.push([index, validator])
		}
	},

	add_mutator:function(index, mutator){
		if (this.after_select) {
			this.mutators[this.to_index(index)].push(mutator)
		}
		else {
			this.pending_mutators.push([index, mutator])
		}
	},

	refresh:function(){
		var current_version = this.provider.current_version(this.refresh_view)
		if(current_version != undefined && 	current_version != this.data.head.revision ){
			this.reload()
			this.data.head.revision = request.transport.responseText
			return true;
		} else {
			return false;
		}
	},

	reload:function(){
			this.operations = []
			this.state = 'empty'
			this.notify_listeners()
	},

	apply_mutators:function(row) {
		var mutated = []
		$A(row).each(function (elem) {mutated.push(elem)})
		for (var i = 0; i < this.data.columns.length; i++) {
			for (var j = 0; j < this.mutators[i].length; j++) {
				mutated[i] = this.mutators[i][j].bind(this)(mutated[i] === null ? '' : mutated[i], mutated)
			}
		}
		return mutated
	},

	validate:function(row, form) {
		var retval = []
		var mutated = this.apply_mutators(row)
		for (var i = 0; i < this.data.columns.length; i++) {
			for (var j = 0; j < this.validators[i].length; j++) {
				var response = this.validators[i][j].bind(this)(mutated[i] === null ? '' : mutated[i], mutated, i, form)
				if (response !== null) {
					retval.push({ column:i, message:response })
					break
				}
			}
		}
		return retval
	},

	flush_operations:function(){
		var success = true
		var result
		try {
			result = this.provider.do_operations(
				this.view,
				{
					operations: this.operations,
					filename_column: this.filename_column
				}
			)
		} catch(e) {
			Kato.Error(e)
			success = false
		}
		this.operations = []
		this.state = 'empty'
		this.filename_column = undefined
		this.notify_listeners()
		Kato.FlushOpsHook(result, success)
		return success
	},

	validate_operations:function(){
		var success = true
		try {
			this.provider.do_operations(
				this.view,
				{
					operations: this.rows_to_validate,
					filename_column: this.filename_column
				},
				{ try_only: true }
			)
		} catch(e) {
			Kato.Error(e)
			success = false
		}
		this.rows_to_validate = []
		return success
	},

	get_empty_row:function(){
		var empty_row =[];
		for (var v=0; v < this.data.columns.length; v++){
			empty_row.push(null)
		}
		return empty_row
	},

	select:function(params, server_params){
		if(params===undefined) params = {}
		if(server_params===undefined) server_params = []
		var local = {}
		var server = {}

		/* if there is 'limit' in server_params add 'offset' and 'order_by_...' and 'regexp_' if they are in params */
		var is_limit = false
		var tmp_map = {}

		for (var j in server_params) {
			tmp_map[server_params[j]] = true
		}

		if (tmp_map['limit'] == true) {
			for (var i in params) {
				if ((i.search(/^orderby_|^offset$|^r?regexp_/) == 0) && (tmp_map[i] != true)) {
						server_params.push(i)
				}
			}
		}

		$H(params).each(function(pair) {
			if ($A(server_params).member(pair[0])) {
				server[pair[0]] = pair[1]
			}
			else {
				local[pair[0]]= pair[1]
			}
		})

		var i = 0
		if (this.state != 'full' ||
		    $H(this.current_filters).size() != $H(server).size() ||
		    $H(this.current_filters).any(function (pair) {
			    return !$H(server).keys().member(pair[0]) ||
				    server[pair[0]] != pair[1]
		    })) {
			//nagme('muszę wykonać requesta\n  view = ' + this.view + '\n  server_params = ' + server_params + '\n  state = ' + this.state + '\n  current_filters = ' + Dumper(this.current_filters) + '\n  server = ' + Dumper(server) + '\n  local = ' + Dumper(local) + '\n  params = ' + Dumper(params))
			this.request_data(server)
			this.current_filters = server
			this.state = 'full'
		}

		var result = new Array()
		var offset = 0
		var limit = 999999 // :-)
		var length = this.data.rows.length
		var fetched = 0;

		var where_text = '({where:function(row){'
		for(var param in local){
			var search = param.match(/^search_(.+)$/);
			if (search){
				if(local[param] === undefined) continue
				if (local[param] === null || local[param] === '') {
					where_text += "return false;"
				}
				else {
					var search_field_index = this.col_name_to_index(search[1])
					if(typeof(local[param])=='string'){
						where_text += "if (row["+search_field_index+"]!='"+local[param]+"') return false;";
					} else {
						where_text += "if (row["+search_field_index+"]!="+local[param]+") return false;";
					}
				}
			}
			var rregexp = param.match(/^rregexp_(.+)$/);
			if (rregexp && local[param]!==''){
				var search_field_index = this.col_name_to_index(rregexp[1])
				where_text += "if (row["+search_field_index+"]===null)return false; if (!row["+search_field_index+"].match(/" + local[param] + "/i)) return false;";
			}
			var regexp = param.match(/^regexp_(.+)$/);
			if (regexp && local[param]!==''){
				var search_field_index = this.col_name_to_index(regexp[1])
				where_text += "if (row["+search_field_index+"]===null)return false; if (!row["+search_field_index+"].match(/" + Kato.Regex.escape(local[param]).replace(/ +/g,'.*') + "/i)) return false;";
			}
			var isnull = param.match(/^isnull_(.+)$/);
			if (isnull && local[param]!=''){
				var search_field_index = this.col_name_to_index(isnull[1])
				where_text += 'if (row["'+search_field_index+'"]'+(local[param] == true ? '!==' : '===')+'null)return false;'
			}

			var search_field_index = param.match(/^[^_]*_(.+)$/) ? this.col_name_to_index(param.match(/^[^_]*_(.+)$/)[1]) : null;

			/* TODO: w tej chwili lt, gt, le i ge porownuja tylko alfabetycznie, a powinny takze numerycznie */

			var lt = param.match(/^lt_(.+)$/);
			if (lt && local[param]!=='' && local[param]!==''){
				var quoted_param = local[param].match(/^[\d.]$/) ? local[param] : "'" + local[param] + "'"
				where_text += "if (row["+search_field_index+"] >= " + quoted_param + ") return false;";
			}
			var ge = param.match(/^ge_(.+)$/);
			if (ge && local[param]!=='' && local[param]!==''){
				var quoted_param = local[param].match(/^[\d.]$/) ? local[param] : "'" + local[param] + "'"
				where_text += "if (row["+search_field_index+"] < " + quoted_param + ") return false;";
			}
			var gt = param.match(/^gt_(.+)$/);
			if (gt && local[param]!=='' && local[param]!==''){
				var quoted_param = local[param].match(/^[\d.]$/) ? local[param] : "'" + local[param] + "'"
				where_text += "if (row["+search_field_index+"] <= " + quoted_param + ") return false;";
			}
			var le = param.match(/^le_(.+)$/);
			if (le && local[param]!=='' && local[param]!==''){
				var quoted_param = local[param].match(/^[\d.]$/) ? local[param] : "'" + local[param] + "'"
				where_text += "if (row["+search_field_index+"] > " + quoted_param + ") return false;";
			}
		}
		where_text += 'return true;}})'
		Kato.Log(where_text,'filter')
		var where = eval(where_text)

		if(local.offset) offset = local.offset
		if(local.limit) limit = local.limit

		//FIXME: depending on whether these is a limit/offset, should sorting be done before or after filtering?

		for(var param in local){
			var search = param.match(/^orderby_(.+)$/);
			if (search) {
				var ordermod = 1;
				if (local[param] == 'desc') {
					ordermod = -1;
				}
				var field_index = this.col_name_to_index(search[1]);
				var sort_function;
				switch (this.data.columns[field_index]['type']) {
					case 'int4':
						sort_function = function(a, b) {
							a = parseInt(a);
							b = parseInt(b);
							if (isNaN(a) && isNaN(b))
								return 0;
							if (a > b || isNaN(a))
								return 1;
							if (a < b || isNaN(b))
								return -1;
							return 0;
		                                };
						break;
					case 'numeric':
						sort_function = function(a, b) {
							a = parseFloat(a);
							b = parseFloat(b);
							if (isNaN(a) && isNaN(b))
								return 0;
							if (a > b || isNaN(a))
								return 1;
							if (a < b || isNaN(b))
								return -1;
							return 0;
		                                };
						break;
					default:
						sort_function = function(a, b) {
							if (a === null && b === null)
								return 0;
							if (a > b || a === null)
								return 1;
							if (a < b || b === null)
								return -1;
							return 0;
		                                };
				}
				this.data.rows.sort(function(a, b) { return sort_function(a[field_index], b[field_index]) * ordermod; });
			}
		}

		var cursor = 0;

		while (cursor<length && fetched < limit){
			var row = this.data.rows[cursor]
			if (where.where(row)){
				if(offset--<=0){
					result.push(row.slice())
					fetched++
				}
			}
			cursor++
		}

		return {
			'columns':this.data.columns,
			'rows':result,
			'head':this.data.head
		}
	},

	request_data:function(params) {
		var saved_revision;
		if(this.view != this.view_refresh && this.data.head) saved_revision = this.data.head.revision

		this.data = this.provider.get_data(this.view, params)

		if (!this.after_select) {
			this.initialize_validators()
			this.initialize_mutators()
			this.after_select = true
		}
		if(saved_revision)this.data.head.revision = saved_revision
	},

	delete_by_index:function(i){
		var row = this.data.rows[i].slice(0)
		var operation = {
			row:			row,
			command:		'delete'
		}

		this.data.rows.splice(i,1)
		this.notify_listeners()
		this.do_operation(operation)
	},

        delete_row:function(row_ref) {
		var row = row_ref.slice(0)
		var operation = {
			row:			row,
			command:		'delete'
		}

		this.data.rows.splice(this.get_index_by_row(row),1);
		this.notify_listeners()
		this.do_operation(operation)
	},

	row_not_null:function(row) {
		var indices = this.get_pk_indices();
		for (var i = 0; i < indices.length; i++)
			if (!row[i])
				return false;
		return true;
	},

	insert:function(row_ref,mask){
		var row = row_ref.slice(0)
		row = this.apply_mutators(row)
		if(row.length!=this.data.columns.length){
			Kato.Error('[Error] Kato.Model, wrong parameter count on insert (' + row + ')')
		}
		if (mask !== undefined){
			var new_row = row.slice(0)
			for (var v = 0; v < mask.length; v++){
				new_row[mask[v]] = row[v]
			}
		}

		var operation = {
			row:		row
		}

		for (var a in row){
			if(row[a]==='')row[a]=null
		}

		if (!this.row_not_null(row) || this.get_pk_indices().length > 1 || this.force_insert) {
			// row[0] = this.get_new_pk() // get_new_pk has been removed, so do not uncomment this one
			this.data.rows.unshift(row)
			//this.notify_listeners()
			operation.command='insert'
		} else {
			var v = this.get_index_by_row(row)
			this.data.rows[v] = row
			//this.notify_listeners()
			operation.command='update'
		}
		this.do_operation(operation);
	},

	insert_for_validation:function(row_ref,mask){

		var row = row_ref.slice(0)
		row = this.apply_mutators(row)
		if(row.length!=this.data.columns.length){
			Kato.Error('[Error] Kato.Model, wrong parameter count on insert (' + row + ')')
		}
		if (mask !== undefined){
			var new_row = row.slice(0)
			for (var v = 0; v < mask.length; v++){
				new_row[mask[v]] = row[v]
			}
		}

		var operation = {
			row:		row
		}

		for (var a in row){
			if(row[a]==='')row[a]=null
		}

		if (!this.row_not_null(row) || this.get_pk_indices().length > 1 || this.force_insert) {
			operation.command='insert'
		} else {
			operation.command='update'
		}
		this.rows_to_validate.push(operation)
	},

	do_operation:function(operation){
		this.operations.push(operation)
		if(this.flush_mode == 'always'){
			return this.flush_operations()
		}
	},

	notify_listeners:function(){
		var view = this.view
		var ping = function(listener){if(Kato.Active[listener])Kato.Active[listener].notify(view)}
		this.listeners.each(ping)
	},

	// by ref
	get_by_pk:function(pk){
		return this.data.rows[parseInt(this.get_index_by_pk(pk))]
	},

	// FIXME: remove this when everything switches to new format of primary keys
	get_index_by_pk_obsolete:function(pk){
		var cursor = 0
		var length = this.data.rows.length
		while(cursor < length){
			if(this.data.rows[cursor][0]===pk) return cursor
			cursor++
		}
		return null
	},

	check_row_pk:function(row, pk, indices) {
		for (var i = 0; i < indices.length; i++)
			if (row[indices[i]] != pk[i])
				return false;
		return true;
	},

	get_pk_indices:function() {
		var result = [];
		for (var i = 0; i < this.data.columns.length; i++) {
			if (this.data.columns[i]['primary position']) {
				result[result.length] = i;
			}
		}
		// falling back
		if (result.length == 0)
			result = [0];
		return result;
	},

        get_index_by_pk:function(pk) {
		if (!Kato.Utils.is_array(pk))
			return this.get_index_by_pk_obsolete(pk);
		var indices = this.get_pk_indices();
		for (var i = 0; i < this.data.rows.length; i++) {
			if (this.check_row_pk(this.data.rows[i], pk, indices)) {
				return i;
			}
		}
		return null;
	},

	get_index_by_row:function(row) {
		var pk_indices = this.get_pk_indices();
		var pk = [];
		for (var i = 0; i < pk_indices.length; i++) {
			pk[pk.length] = row[i];
		}
		return this.get_index_by_pk(pk);
	},

	count:function(params, server_params){
		if (server_params === undefined) server_params = []
		// eksperyment...
		var server_offset = false;
		for (var v=0; v < server_params.length; v++){
			if(server_params[v]=='offset'){
				server_offset=true
				break;
			}
		}
		return this.data.rows.length + (server_offset? params.offset : 0)
		//return this.select(count_params, server_params).rows.length
	},

	col_name_to_index:function(col_name){
		for (var v=0; v<this.data.columns.length; v++){
			if (this.data.columns[v].column == col_name) return v;
		}
		return false
	},

	hash_from_row:function(row){
		var hash = {}
		for (var v=0; v < row.length; v++){
			hash[this.data.columns[v].column] = row[v]
		}
		return hash
	},

	to_index:function(val){
		return isNaN(parseInt(val)) ? this.col_name_to_index(val) : val
	}
});


Kato.Container = Class.create();
Kato.Container.prototype = Object.extend({},{
	switchboard: undefined,
	panels: undefined,

	initialize:function(element){
		element = $(element)
		this.element = $(element);
		this.my_initialize();
	},

	my_initialize:function(){
	},

	build:function(){
		this.id = this.element.id
		this.panels = [];
		this.panel_displayed = null
		var temp = this.element.childNodes;
		for (var v=0; v < temp.length; v++){
			if(!temp[v].getAttribute)continue;
			this.panels.push({
				name:		temp[v].getAttribute('kato_name')?temp[v].getAttribute('kato_name'):temp[v].id,
				element:	temp[v],
				id:			temp[v].id,
				available:	temp[v].getAttribute('kato_available')?eval(temp[v].getAttribute('kato_available')):true
			})
		}
		this.switchboard = document.createElement('div');
		this.switchboard.className='switchboard'
		this.element.insertBefore(this.switchboard,this.element.firstChild);
		if(this.panel_displayed==null){
			this.panel_displayed=0
		}
		this.show_panel(0)
	},
	panel_available:function(panel, is_available){
		for (var v=0; v < this.panels.length; v++){
			if(v==panel || this.panels[v].id == panel){
				this.panels[v].available = is_available
			}
		}
		this.render()
	},

	render:function(){
		var o = '';
		for (var v=0; v < this.panels.length; v++){
			if(!this.panels[v].available)continue
			var classname = ((v==this.panel_displayed) ? 'selected' : '')
			o += '<a href="#" class="' + classname + '" onclick="Kato.Active.' + this.id + '.show_panel(' + v + '); return false">'
			o += this.panels[v].name
			o += '</a> '
		}
		this.switchboard.innerHTML = o
		if(this.custom_after_render) this.custom_after_render.bind(this)()
	},

	allow_switch:function(which) {
		return true;
	},

	show_panel:function(which){
		if (!this.allow_switch(which))
			return;
		for (var v=0; v < this.panels.length ; v++){
			this.panels[v].element.style.display = 'none'
		}
		this.panels[which].element.style.display = ''
		this.panel_displayed = which
		this.render()
	}
});


Kato.View = Class.create();
Kato.View.prototype = Object.extend ({},{
	params:				undefined,
	server_params:			undefined,
	change_handlers:		undefined,
	model:				undefined,
	viewname:			undefined,
	id:				undefined,
	value:				undefined,
	text:				undefined,
	onclick_notify_children:	undefined,
	params_template:		undefined,
	changed_rows:			undefined,
	deaf:				false,
	blocks:				undefined,
	column_displayed:		undefined,
	column_displayed_index:		undefined,

	component_template:undefined,


	initialize:function(element){
		element = $(element)
		this.element = $(element);

		this.my_initialize()

		if (element.hasAttribute('kato_model')){
			this.viewname = element.getAttribute('kato_model')
		} else {
			this.viewname = 'dummy_model'
		}

		if (element.hasAttribute('kato_cell_binding')){
			this.cell_binding = eval('([' + element.getAttribute('kato_cell_binding') + '])')
		}
		if (element.hasAttribute('kato_params_constraint')){
			this.params_constraint = eval('({' + element.getAttribute('kato_params_constraint') + '})')
		}
		if (element.hasAttribute('kato_value')){
			this.value = element.getAttribute('kato_value')
		} else {
			this.value = ''
		}
		if (element.hasAttribute('kato_text')){	// używany przez Kato.Combo do trzymania tekstu inputa
			this.text = element.getAttribute('kato_text')
		} else {
			this.text = ''
		}
		if(this.params_template===undefined){
			this.params_template = {}
		}
		if (element.hasAttribute('kato_params_template')){
			Object.extend(this.params_template, eval('({' + element.getAttribute('kato_params_template') + '})'))
		}
		if (element.hasAttribute('kato_server_params')){
			this.server_params = eval('([' + element.getAttribute('kato_server_params') + '])')
		}
		if (element.hasAttribute('kato_params_translation_table')){
			this.params_translation_table = eval('({' + element.getAttribute('kato_params_translation_table') + '})')
		}
		if (element.hasAttribute('kato_onclick_notify_children')){
			this.onclick_notify_children = eval('([' + element.getAttribute('kato_onclick_notify_children') + '])')
		}
		this.change_handlers = Kato.Extract(element,'kato_onchange_handlers',[])
		this.onsent_handlers = Kato.Extract(element,'kato_onsent_handlers',[])
		this.afterfetch_handlers = Kato.Extract(element,'kato_afterfetch_handlers',[])

		this.user_initialize()
	},

	user_initialize:function(){

	},


	destroy:function(){
		this.model.remove_listener(this)
	},

	move_into:function(what,before){
		if(before==null) before = $(what).firstChild
		$(what).insertBefore(this.element,before)
		this.element.style.display = ''
		this.focus()
	},

	hide:function(){
		this.element.display = 'none'
	},

	focus:function(){
		var inputs = this.element.getElementsByTagName('input');
		if(inputs.length>0){
			inputs[0].focus()
		}
	},

	build:function(element){
		this.params = {}
		this.model = Kato.GetModel(this.viewname)
		this.changed_rows = $H()
		this.id = this.element.id
		if(this.cell_binding && this.cell_binding[2]===undefined)this.cell_binding[2]=0
		if(!this.onclick_notify_children)this.onclick_notify_children = new Array()
		if(!this.server_params)this.server_params = new Array()

		this.model.add_listener(this.id)
		if(this.add_mutators) this.add_mutators()
		if(this.add_validators) this.add_validators()
		this.reset_params()
		if(this.element.hasAttribute('kato_blocks')){
			this.blocks = Kato.Extract(this.element, 'kato_blocks', {})
		}
		if(this.element.hasAttribute('kato_column_displayed')){
			this.column_displayed=this.element.getAttribute('kato_column_displayed')
		}
	},

	has_block:function(block_name){
		if(this.blocks==undefined) return true;
		return this.blocks[block_name] != undefined ?  this.blocks[block_name] : true;
	},

	constraint_row:function(row){
		var col = []
		for (var param in this.params){
			if (col = param.match(/^search_(.*)$/i)){
				row[this.model.col_name_to_index(col[1])] = this.params[param]
			}
		}
		return row
	},

	constraint_form_data:function(){
		this.constraint_row(this.rows[0])
	},

	send_data:function(){
		if (this.before_send_data){
			this.before_send_data()
		}
		var temporary_mode = this.model.flush_mode
		if (this.hide_custom_error_messages) this.hide_custom_error_messages()
		this.hide_error_messages()

		if(!this.validate_rows()){
			return false
		}

		var rows_to_insert = []
		this.changed_rows.each(function(row_changed){
			rows_to_insert.push((this.rows[row_changed.key]))
		}.bind(this))

		rows_to_insert.each(function(r){this.model.insert_for_validation(r)}.bind(this))

		if (!this.model.validate_operations()) {
			return false
		}

		this.model.flush_mode = 'none'
		var deaf_holder = this.deaf;
		this.deaf = true;

		rows_to_insert.each(function(r){this.model.insert(r)}.bind(this))

		this.model.notify_listeners();
		this.deaf = false
		if (this.changed_rows.size()) {
			this.deaf = deaf_holder;
			this.model.flush_operations()
		}
		this.model.flush_mode = temporary_mode
		this.deaf = deaf_holder;
		this.changed_rows = $H()
		if (this.after_send_data) this.after_send_data()
		return true
	},

	validate_rows:function() {
		var validated = true
		this.changed_rows.each(function(row_changed){
			// hic sunt errorae
			if(!this.validate_row(row_changed.key)){
				validated = false
			}
		}, this)
		return validated
	},

	validate_row:function(row_index) {
  	        var errors = this.model.validate(this.rows[row_index], this)
		if (errors.length == 0) {
			return true
		}
		this.handle_errors(errors)
		return false
	},

	hide_error_messages:function(){},

	handle_errors:function(errors){
		for (var i = 0; i < errors.length; i++) {
			alert(this.model.data.columns[errors[i].column].column + ': ' + errors[i].message)
			this.handle_error(errors[i])
		}
	},

	handle_error:function(error){
		alert(Kato.Translate(error.message))
	},

	col_name_to_index:function(v){
		return this.model.col_name_to_index(v)
	},


	set_form:function(data,rerender){
		for (key in data){
			this.set_form_cell(key,data[key],rerender)
		}
	},

	set_form_cell:function(col_name, col_value,rerender){
		this.set_cell(0,this.model.col_name_to_index(col_name),col_value,rerender)
	},

	set_cell:function(row,col,value,rerender){
		if(value===''){
			value = null;
		}
		this.rows[row][col] = value;
		this.changed_rows.set(row, true)
		if(rerender){
			this.render(undefined, false)
		}
	},

	notify:function(who){
		if(this.deaf) return
		this.render()
	},

	mark_last_clicked_row:function(){
		var row_elements = this.element.getElementsByTagName('tr')
		for (var v = 0; v < row_elements.length; v++){
			if (v-1 == this.last_clicked_row){
				$(row_elements[v]).addClassName('kato_selected')
			} else {
				$(row_elements[v]).removeClassName('kato_selected')
			}
		}
	},

	row_clicked:function(ids,row_index){
		this.last_clicked_row = row_index;
		this.mark_last_clicked_row()

		var params = {}
		var pk = this.model.get_pk_indices()
		for (var i = 0; i < pk.length; i++) {
			params['search_'+this.columns[pk[i]].column] = String(ids[i])
		}
		this.onclick_notify_children.each(function(childname){
			Kato.Active[childname].change_params(params)
		})
		for (var v=0; v<this.change_handlers.length; v++){
			this.change_handlers[v](this,ids,row_index)
		}
	},

	translate_params:function(params){
		var params_translated = {}
		for (var k in params) {
			var nowe_k = k
			if (this.params_translation_table && this.params_translation_table[k]) nowe_k = this.params_translation_table[k]
			params_translated[nowe_k] = params[k]
		}
		return(params_translated)
	},

	change_params:function(params){
		this.render(this.translate_params(params))
	},

	reset_params:function(){
		this.params = Object.extend({},this.params_template)
	},

	reset_orderby_params: function() {
		var newparams = {};
		$H(this.params).each(function(pair) {
			if (!pair[0].match(/^orderby_/)) {
				newparams[pair[0]] = pair[1];
			}
		});
		this.params = newparams;
	},

	remove_from_params:function(params){
		if (params.constructor === String) params = [ params ];
		var rerender = false
		var newparams = {}
		$H(this.params).each(function (pair) {
			if (!params.include(pair[0])) {
				newparams[pair[0]] = pair[1]
			}
			else {
				rerender = true
			}
		})
	        this.params = newparams
		if (rerender) {
			this.render()
		}
	},

	fetch_data:function(params, server_params){
		var params_constrained = Object.extend(Object.extend({},params),this.params_constraint)
		Kato.Log(params_constrained)
		var deaf_holder = this.deaf;
		this.deaf=true;
		//nagme(this.id + ' will ask for data\nparams = ' + Dumper(params) + '\nserver_params = ' + Dumper(server_params))
		var data = this.model.select(params, server_params)
		this.count = this.model.count(params, server_params)
		this.deaf=deaf_holder;
		this.rows = data.rows
		this.head = data.head
		this.columns = data.columns
		if(this.initialize_cell_renderers) this.initialize_cell_renderers()
		if(this.initialize_visible_columns) this.initialize_visible_columns()
		if(this.initialize_invisible_columns) this.initialize_invisible_columns()
		if(this.initialize_column_order) this.initialize_column_order()
		for(var v=0; v < this.cell_renderers.length; v++ ){
			if(this.cell_renderers[v]){
				this.first_cell_renderer_index = v
				break;
			}
		}
		if(this.column_displayed_index==undefined){
			if(this.column_displayed!=undefined){
				this.column_displayed_index = this.model.col_name_to_index(this.column_displayed)
			} else {
				this.column_displayed_index=this.first_cell_renderer_index
			}
		}
		if(this.after_fetch_data) this.after_fetch_data()
		for (var v=0; v<this.afterfetch_handlers.length; v++){
			this.afterfetch_handlers[v].bind(this)()
		}
	},

	initialize_cell_renderers:function(){
		this.cell_renderers = new Array();
		var user_cell_renderers = Kato.Extract(this.element, 'kato_custom_cell_renderers', {})

		for (var v=0; v<this.columns.length; v++){
			if(user_cell_renderers[this.columns[v].column]){
				this.cell_renderers[v] = (typeof user_cell_renderers[this.columns[v].column] == 'function' ?
							  user_cell_renderers[this.columns[v].column].bind(this) :
							  user_cell_renderers[this.columns[v].column])
			} else {
				if (Kato.Renderers.DefaultRenderers[this.columns[v].type]) {
					this.cell_renderers[v] = Kato.Renderers.DefaultRenderers[this.columns[v].type].bind(this)
				} else {
					this.cell_renderers[v] = this.build_cell.bind(this)
				}
			}
		}
		this.user_cell_renderers = user_cell_renderers
	},

	initialize_invisible_columns:function(){
		var user_invisible_columns = Kato.Extract(this.element, 'kato_invisible_columns', [])
		for (var i=0; i<this.columns.length; i++){
			for (var j=0; j<user_invisible_columns.length; j++){
				if (this.columns[i].column == user_invisible_columns[j]) {
					this.cell_renderers[i] = null
				}
			}
		}
	},

	initialize_visible_columns:function(){
		var user_visible_columns = Kato.Extract(this.element, 'kato_visible_columns', [])
		if(user_visible_columns.length==0){
			for (var v=0; v<this.columns.length; v++){
				if(
					(
						this.columns[v].column.match(Kato.Config.KeyColumnRegex)
						|| this.columns[v].column == 'custom_columns'
					)
					&& (!this.user_cell_renderers[this.columns[v].column])
				){
					this.cell_renderers[v] = null
				}
			}
		}
		else {
			for (var v=0; v<this.columns.length; v++){
				if(!user_visible_columns.map(this.model.to_index.bind(this.model)).include(v)){
					this.cell_renderers[v] = null
				}
			}
		}

	},

	initialize_column_order:function(){
		var column_order = Kato.Extract(this.element, 'kato_column_order', [])
		var column_order_map = []
		if (column_order.size() == 1 && column_order[0] == 'same_as_visible_columns' && this.element.hasAttribute('kato_visible_columns')) {
			column_order = Kato.Extract(this.element, 'kato_visible_columns', [])
		}
		column_order.each(function (elem) { var t = this.model.to_index.bind(this)(elem); if (t !== false) column_order_map.push(t) }, this)
		this.columns.length.times(function (n) { if (!column_order_map.include(n)) column_order_map.push(n) })
		this.column_order_map = column_order_map
	},

	add_mutators:function(){
		var user_custom_mutators = Kato.Extract(this.element, 'kato_custom_mutators', {})
		var model = this.model
		$H(user_custom_mutators).each(function (pair) {
			$A(typeof pair.value == 'function' ? [ pair.value ] : pair.value).each(function (mutator) {
				model.add_mutator(pair.key, mutator)
			})
		})
	},

	add_validators:function(){
		var user_custom_validators = Kato.Extract(this.element, 'kato_custom_validators', {})
		var model = this.model
		$H(user_custom_validators).each(function (pair) {
			$A(typeof pair.value == 'function' ? [ pair.value ] : pair.value).each(function (validator) {
				model.add_validator(pair.key, validator)
			})
		})

		var user_novalidate_columns = Kato.Extract(this.element, 'kato_novalidate_columns', [])
		user_novalidate_columns.each(function(elem) { this.validators[this.to_index(elem)] = [] }, this.model)
	},

	build_cell:function(row,index){
		var o = '';
		o += '	<td>'
//		o += (['', null ].include(row[index])) ? '&nbsp;' : row[index]
		o += row[index] === null || row[index] === '' ? '&nbsp;' : row[index]
		o += "</td>\n"
		return o
	},

	push_empty_row:function(){
		var empty_row = this.model.get_empty_row();
		this.constraint_row(empty_row)
		this.rows.push(empty_row)
		this.render({}, false)
	},

	render:function(params, fetch_data){
                if(this.before_render)this.before_render()
		if(fetch_data==undefined) fetch_data = true
		if (params===undefined) var params = {}
		this.params = Object.extend(this.params,params)
		if(fetch_data){
			this.fetch_data(this.params, this.server_params)
	                this.empty_rows = (this.rows.length == 0)
		}
		if(this.rows.length==0){
			this.push_empty_row()
			if(this.constraint_form_data) this.constraint_form_data()
		}
		this.uploadcounter = 0;
		this.uploads = new Array();
		this.build_html();
		this.last_clicked_row = null
		this.element.innerHTML = this.built_html
		if(this.after_render)this.after_render()
		if(this.custom_after_render) this.custom_after_render.bind(this)()
	}
});

Kato.Dropdown = Class.create();
Kato.Dropdown.prototype = Object.extend({},Kato.View.prototype)

Object.extend(Kato.Dropdown.prototype, {
	value:null,
	null_option:undefined,

	my_initialize:function(){
		this.null_option = Kato.Extract(this.element, 'kato_null_option', true, true)
	},

	build_html:function(){
		this.built_html = '';
		this.built_html += this.build_header()
		this.built_html += this.build_rows()
		this.built_html += this.build_footer()
	},

	build_header:function(){
		var o = ''
		o += '<select onclick="return false" onchange="Kato.Active.' + this.id + '.row_clicked_dropdown(this.options[this.selectedIndex].value)" id="' + this.id + 'Kselect">'
		return o
	},

	row_clicked_dropdown:function(id){
		this.value = id
		if(this.cell_binding){
			Kato.Active[this.cell_binding[0]].set_cell(this.cell_binding[2],this.cell_binding[1],id)
		}
		this.row_clicked(id)
	},

	build_footer:function(){
		var o = '';
		o += '</select>'
		return o
	},

	build_rows:function(){
		var o = ''
		if (this.null_option) {
			o += '<option value="">---</option>'
		}
		else {
			if (this.cell_binding && this.rows[0] && this.rows[0][0]) {
				Kato.Active[this.cell_binding[0]].set_cell(this.cell_binding[2],this.cell_binding[1],this.rows[0][0])
			}
		}
		for(var v=0; v< this.rows.length; v++){
			o += this.build_row(this.rows[v],v)
		}
		return o;
	},

	build_row:function(row){
		if (typeof(row[0])=="undefined" || row[0]===null) return
		var o = ''
		o += '<option value="' + row[0] + '"'
		if (this.value == row[0]) o += " selected='selected' "
		o += '>'
		o += this.cell_renderers[this.column_displayed_index](row,this.column_displayed_index)
		o += "</option>\n"
		return o;
	},

	build_cell:function(row,index){
		return row[index]
	}
})


Kato.Detail = Class.create();
Kato.Detail.prototype = Object.extend({},{

	my_initialize:function(){
		this.params_template = {offset: 0, limit:1}
	},

	build_html:function(){
		this.built_html = '';
		this.built_html += this.build_controls()
		this.built_html += this.build_header()
		this.built_html += this.build_rows()
		this.built_html += this.build_footer()
	},

	build_header:function(){
		var o = '';
		o += '<table class="kato_detail kato">'
		return o
	},

	build_rows:function(){
		var o = '';
		for(var v=0; v< this.columns.length; v++){
			var vm = this.column_order_map[v]
			if(this.cell_renderers[vm]){
				o += this.build_row(this.rows[0],vm)
			}
		}
		return o
	},

	build_footer:function(){
		var o = '';
		o += '</table>'
		return o
	},

	build_controls:function(){
		var o = '';
		return o
	},

	build_row:function(row,index){
		var o = '';
		o += '<tr><th style="width: 30%">' + Kato.Translate(this.columns[index].column) + '</th>'
		o += this.cell_renderers[index](row,index,this.id,0)
		return o
	}
});

Object.extend(Kato.Detail.prototype,Kato.View.prototype)

Kato.Form = Class.create()
Kato.Form.prototype = Object.extend({},Kato.Detail.prototype)
Kato.Form.prototype = Object.extend(Kato.Form.prototype,{
	my_initialize:function(){
		this.params_template = {offset: 0, limit:1}
	},

	error_mode:false,
	error_specs:null,
        insert_mode:false,

	upload_file:function(i) {
		var form = $(this.id + 'KuploadformK' + i);
		form.submit();
	},

	upload_files:function() {
		for (var i = 0; i < this.uploads.length; i++) {
			if (this.uploads[i])
				this.upload_file(i);
		}
		setTimeout(this.check_uploads.bind(this), 100);
	},

	check_uploads:function() {
		console.log(this);
		var ok = true;
		for (var i = 0; i < this.uploads.length; i++) {
			if (this.uploads[i]) {
				var id = this.id + 'KuploadK' + i;
				var nid = $(id).contentWindow.document.body.innerHTML;
				if (nid == '')
					ok = false;
				else
					this.rows[0][Kato.Uploads[id].column_index] = parseInt(nid);
			}
		}
		setTimeout((ok ? (this.sending_as_new ? this.send_form_as_new_bh : this.send_form_bh) : this.check_uploads).bind(this), 100);
	},

	send_form:function(){
		this.deaf_holder = this.deaf;
		this.deaf = true;
		if (this.uploads) {
			this.sending_as_new = false;
			this.upload_files();
		} else {
			this.send_form_bh();
		}
	},

	send_form_bh:function() {
		try {
			var is_insert = !this.model.row_not_null(this.rows[0])
		        this.insert_mode = is_insert
			var successful = false
			if(this.send_data()){
				successful = true
				if (this.after_operation) this.after_operation('send')
			}
			for (var v=0; v<this.onsent_handlers.length; v++){
				this.onsent_handlers[v].bind(this)()
			}
			if (is_insert && successful) {
				this.clear_form()
			}
		} catch (e) {

		}
		this.deaf = this.deaf_holder;
	},

	send_form_as_new:function() {
		if (this.uploads) {
			this.sending_as_new = true;
			this.upload_files();
		} else {
			this.send_form_as_new_bh();
		}
	},

	send_form_as_new_bh:function(){
		var i
		var old_keys = []
		var pk_indices = this.model.get_pk_indices()
		for (i = 0; i < pk_indices.length; i++) {
			old_keys[old_keys.length] = this.rows[0][pk_indices[i]]
			this.rows[0][pk_indices[i]] = null
		}
	        this.insert_mode = true
		if(this.send_data()){
			this.clear_form()
			if (this.after_operation) this.after_operation('send')
		}
		else {
			for (i = 0; i < pk_indices.length; i++)
				this.rows[0][pk_indices[i]] = old_keys[i]
		}
		for (var v=0; v<this.onsent_handlers.length; v++){
			this.onsent_handlers[v].bind(this)()
		}
	},

	handle_errors:function(errorspecs) {
		this.error_mode = true;
		this.error_specs = [];
		for (var i = 0; i < errorspecs.length; i++) {
			this.error_specs[errorspecs[i].column] = errorspecs[i].message;
		}
		this.render({}, false);
		this.error_mode = false;
	},

	handle_error:function(errorspec) {
		var done = false
		if (this.custom_handle_error) {
			done = this.custom_handle_error(errorspec)
		}
		if (done) return

		/* col is the index in the this.columns array pointing to the column in which the error occurred */
		var col = this.column_order_map.indexOf(errorspec.column)
		/* field is the number of non-null cell renderers that precede the renderer of that column */
		var field = $R(0, col, true).inject(0, function(acc, val) {
			/* this is straightforward: we collect the number of non-null cell renderers
			 * by looking at the this.cell_renderers array under indices from 0 to col inclusive
			 * after translating them with this.column_order_map
			 */
			if (this.cell_renderers[this.column_order_map[val]]) { return acc + 1 }
			else { return acc }
		}, this)
		if (this.cell_renderers[field]) {
			var input = this.element.descendants().select(function (elem) {
				return ['input', 'select', 'textarea'].include(elem.tagName.toLowerCase())
			})[field]
			// harharhar
			input.style.backgroundColor = '#FF9595'
		}
		var li = $(this.errors_list).descendants()[col]
		li.innerHTML = ''
		li.innerHTML += Kato.Translate(this.columns[errorspec.column].column)
		li.innerHTML += ': '
		li.innerHTML += errorspec.message
		li.show()
		return true
	},

	hide_error_messages:function() {
		$(this.errors_list).descendants().each(Element.hide)
		$A(this.element.getElementsByTagName('input')).each(function (elem) {elem.style.backgroundColor = ''})
	},

	hide_custom_error_messages:function() {
	},

	delete_form:function(){
		if (this.model.row_not_null(this.rows[0])) {
			this.model.delete_row(this.rows[0])
		}
		this.clear_form()
		if (this.after_operation) this.after_operation('delete')
		for (var v=0; v < this.onsent_handlers.length; v++){
			this.onsent_handlers[v].bind(this)()
		}
	},

	clear_form:function(rerender){
		if(rerender==undefined) rerender = true

		var old_row = this.rows[0]
		this.rows[0] = new Array()

		for (var v = 0; v < this.columns.length; v++){
			this.rows[0].push(null)
		}

		this.constraint_form_data()
		var pk_indices = this.model.get_pk_indices()
		if (pk_indices.length == 1) {
			for (i = 0; i < pk_indices.length; i++) {
				this.rows[0][pk_indices[i]] = null
			}
		}
		if(rerender){
			this.build_html();
			this.element.innerHTML = this.built_html
			if(this.after_render)this.after_render()
			if(this.custom_after_render) this.custom_after_render.bind(this)()
		}
	},

	build_row:function(row,index){
		var o = ''

		var block = this.has_block('before_' + this.columns[index].column)

		if (typeof(block) == 'string'){
			o += block
		}

		var value = row[index]===null?'':row[index]
		if (typeof(this.cell_renderers[index]) == 'function') {
			return o + this.cell_renderers[index].bind(this)(row, index, this.id, 0, value)
		}
		/* renderer jest stringiem, czyli np. dropdownem. Trzeba stworzyć dla niego puste td */
		return o + this.build_cell(row, index)
	},

	build_cell:function(row, index, id, row_index, value) {
		var o = ''
		o += '<tr><th style="width: 30%">' + Kato.Utils.column_to_desc(this.columns[index]) + '</th>'
		var type = this.columns[index].column.match(/password|haslo/) ? 'password' : 'text';
		var style = (this.error_mode && this.error_specs[index] !== undefined) ? 'style="background-color: #FF9595" ' : '';
		o += '<td id="' +this.id + 'Krow_' + this.columns[index].column + '"><input class="text" type="' + type + '" ' + style + 'onkeyup="Kato.Active.' + this.id + '.set_cell(0,' + index + ',this.value)" value="' + (value ? Kato.Utils.escape_quotes(value) : '') + '"/>'
		o += '</td></tr>'
		if (value!==undefined) {
			this.set_cell(0, index, value)
		}
		return o
	},

	build_footer:function(){
		var o = '';
		o += '<tr><td colspan="2">'

		if(this.has_block('form_edit'))
			o += '<input type="button" class="kato_button" value="' + Kato.Translate('edit') + '" onclick="Kato.Active.' + this.id + '.send_form()" />'
		if(this.has_block('form_save_new'))
			o += '<input type="button" class="kato_button" value="' + Kato.Translate('save_new') + '" onclick="Kato.Active.' + this.id + '.send_form_as_new()" />'
		if(this.has_block('form_new_item'))
			o += '<input type="button" class="kato_button" value="' + Kato.Translate('new_item') + '" onclick="Kato.Active.' + this.id + '.clear_form()" />'
		if(!this.has_block('no_form_careful_new_item'))
			o += '<input type="button" class="kato_button" value="' + Kato.Translate('new_item') + '" onclick="if(confirm(Kato.Translate(\'confirm_new_item\')))Kato.Active.' + this.id + '.clear_form()" />'
		if(this.has_block('form_remove'))
			o += ' | <input type="button" class="kato_button" value="' + Kato.Translate('remove') + '" onclick="Kato.Active.' + this.id + '.delete_form()" />'
		if(!this.has_block('no_form_careful_remove'))
			o += ' | <input type="button" class="kato_button" value="' + Kato.Translate('remove') + '" onclick="if(confirm(Kato.Translate(\'confirm_remove\')))Kato.Active.' + this.id + '.delete_form()" />'
		if(typeof(this.has_block('form_custom_buttons'))=='function'){
			o += this.has_block('form_custom_buttons').bind(this)()
		}
		o += '</td></tr>';
		if (this.error_mode) {
			o += '<tr><td colspan="2"><ul>';
			for (var i = 0; i < this.error_specs.length; i++) {
				if (this.error_specs[i]) {
					o += '<li class="error_message">' + Kato.Translate(this.columns[i].column) + ': ' + this.error_specs[i] + '</li>';
				}
			}
			o += '</ul></td></tr>';
		} else {
			o += '<ul style="display: none;"></ul>'
		}
		o += '</table>';
		return o
	},

	after_render:function(){
		for(var v = 0; v < this.columns.length; v++){
			if(this.cell_renderers[v]=='dropdown' || this.cell_renderers[v]=='combo'){
				var params = {}
				params.id = this.id + 'Kdropdown_' + this.columns[v].column
				params.model = Kato.PrimaryKeys[this.columns[v].column]
				if(!Kato.PrimaryKeys[this.columns[v].column]){
					Kato.Error('wrong model for key ' + this.columns[v].column)
				}
				params.type = this.cell_renderers[v]=='dropdown'?'Kato.Dropdown':'Kato.Combo'
				params.cell_binding= "'" + this.id + "'," + v
				Kato.BuildFromScratch(params,$(this.id + 'Krow_' + this.columns[v].column))
			}
		}
		if(this.custom_after_render) this.custom_after_render.bind(this)()
		for(var v = 0; v < this.columns.length; v++){
			if(this.cell_renderers[v]=='dropdown' || this.cell_renderers[v]=='combo'){
				var id = this.id + 'Kdropdown_' + this.columns[v].column
				Kato.Active[id].value = this.rows[0][v]
				Kato.Active[id].render()
			}
		}
		this.errors_list = this.element.getElementsByTagName('ul')[0]
	},
	custom_after_render:function(){
	}
})

Kato.Grid = Class.create();
Kato.Grid.prototype = Object.extend ({},{
	page:				0,

	my_initialize:function(){
		if(!this.params_template){
			this.params_template = {offset: 0, limit:50}
		}
		this.column_widths = Kato.Extract(this.element,'kato_column_widths',{})
		this.clickable_column_headers = Kato.Extract(this.element, 'kato_clickable_column_headers', true, true);
	},

	page_flip_next:function(){
		if(!this.rows.length) return;
		this.send_data()
		this.page++
		this.render()
	},

	page_flip_previous:function(){
		if(this.page<=0)return;
		this.send_data()
		this.page--
		this.render()
	},


	before_render:function(){

		this.save_th_widths()
	},

	after_render:function(){
		this.set_th_widths()

		var view = this;
		var change_sorting = function() {
			col = parseInt(this.getAttribute('column_index'));
			var params = {};
			if (view.params['orderby_'+view.columns[col].column] === undefined ||
				view.params['orderby_'+view.columns[col].column] == 'desc') {

				params['orderby_'+view.columns[col].column] = 'asc';
			} else {
				params['orderby_'+view.columns[col].column] = 'desc';
			}
			view.reset_orderby_params();
			view.change_params(params);
		}

		if (this.clickable_column_headers) {
			var ths = this.element.getElementsByTagName('th');
			for (var i=0; i < ths.length; i++) {
				$(ths[i]).observe('click', change_sorting);
			}
		}
	},

	save_th_widths:function(){
		this.saved_th_widths = []
		var ths = this.element.getElementsByTagName('th')
		for (var v=0; v < ths.length; v++){
			this.saved_th_widths[v] = ths[v].style.width
		}
	},

	set_th_widths:function(){
		var ths = this.element.getElementsByTagName('th')
		if (!this.saved_th_widths){
			for (var v=0; v < ths.length; v++){
				ths[v].style.width = ths[v].style.width
			}
		} else {
			for (var v=0; v < ths.length; v++){
				if(this.saved_th_widths[v]) {
					ths[v].style.width = this.saved_th_widths[v]
				}
			}

		}
	},

	insert_new_row:function(){
		if (!this.model.row_not_null(this.rows[0])) return;
		this.push_empty_row()
		this.render({},false)
	},

	delete_last_clicked_row:function(){
		if (this.last_clicked_row === null || this.last_clicked_row === undefined) return
		this.delete_from(this.last_clicked_row)
		this.last_clicked_row = null
	},

	delete_from:function(row_index){
		this.model.delete_by_index(row_index)
	},

	build_html:function(){
		this.built_html = '';
		if(this.has_block('grid_controls')==true){
			this.built_html += this.build_controls()
		}
		this.built_html += this.build_header()
		this.built_html += this.build_rows()
		this.built_html += this.build_footer()
		if(this.has_block('grid_controls')=='bottom'){
			this.built_html += this.build_controls()
		}
	},

	build_header:function(){
		var o = '<div style="overflow: auto"><table class="kato kato_table">\n';
		if(this.has_block('grid_header')){
			var ths = '<tr>'
			this.number_of_columns = 0;
			for (var v = 0; v < this.columns.length; v++) {
				var vm = this.column_order_map[v]
				if (this.cell_renderers[vm]) {
					ths += '<th';
					if ((this.column_widths) && (this.column_widths[this.columns[vm].column])) {
	//				alert(this.column_widths[this.columns[v].column]);
						ths += ' style="width: ' + this.column_widths[this.columns[vm].column]
						if (this.column_widths[this.columns[vm].column].match(/\d$/)) {
							 ths += 'px"';
						}
						else {
							ths += '"';
						}
					}
					ths += ' column_index="' + vm + '"';
					ths += '>';
					if (v!=this.columns.length-1) ths += '<span onmousedown="Kato.Resize(this.parentNode,Event.pointerX(event)); Event.stop(event)" style="position: relative; float: right; height: 100%; width: 10px; cursor:e-resize">&nbsp;</span>'
					ths += Kato.Translate(this.columns[vm].column);
					if (this.params['orderby_'+this.columns[vm].column] !== undefined) {
						if (this.params['orderby_'+this.columns[vm].column] == 'desc') {
							ths += '&nbsp;&#9650;';
						} else {
							ths += '&nbsp;&#9660;';
						}
					}
					ths += '</th>';
					this.number_of_columns++
				}
			}
			o += ths
			o += '</tr>'
		}
		return o;
	},

	build_rows:function(){
		var o = ''
		for(var v=0; v< this.rows.length; v++){
			o += this.build_row(this.rows[v],v)
		}
		return o;
	},

	build_row:function(row,row_number){
		var o = ''
		if(this.has_block('grid_highlight_rows')){
			var pk_indices = this.model.get_pk_indices()
			o += "<tr class='kato_clickable' onclick='Kato.Active." + this.id + ".row_clicked(["
			for (var i = 0; i < pk_indices.length; i++) {
				o += "\"" + row[pk_indices[i]] + "\", "
			}
			o += "], " + row_number + ")' onmouseout='$(this).removeClassName(\"kato_hover\")' onmouseover='$(this).addClassName(\"kato_hover\")'>\n"
		} else {
			o += "<tr>\n"
		}
		for(var v=0; v<row.length; v++){
			var vm = this.column_order_map[v]
			if(this.cell_renderers[vm]){
				o += this.cell_renderers[vm](row,vm,this.id, row_number);
			}
		}
		o += "</tr>\n"
		return o
	},

	build_footer:function(){
		var o = ''
		o += '</table></div>'

		return o;
	},

	build_controls:function(){
		var o = '<div class="controls">';
		if (this.page > 0){
			o += "<a href='' onclick='Kato.Active."+this.id+".page_flip_previous(); return false'>" + Kato.Translate('prev_page')+ "</a>"
		} else {
			o += Kato.Translate('prev_page')
		}
		o += ' | '
		if (this.page < this.last_page){
			o += "<a href='' onclick='Kato.Active."+this.id+".page_flip_next(); return false'>" + Kato.Translate('next_page')+ "</a>"
		} else {
			o += Kato.Translate('next_page')
		}
		o += "</div>"
		return o
	}
});

Object.extend(Kato.Grid.prototype,Kato.View.prototype)

Kato.Grid.prototype.change_params=function(params){
		this.page = 0
		this.render(this.translate_params(params))
}

Kato.Grid.prototype.render=function(params, fetch_data){
		this.before_render()
		if(fetch_data==undefined) fetch_data = true
		if (params===undefined) var params = {}
		this.params = Object.extend(this.params,params)
		this.params.offset = this.page * this.params.limit
		if(fetch_data) this.fetch_data(this.params, this.server_params)
		this.last_page = Math.floor(this.count / this.params.limit)
		this.build_html();
		this.element.innerHTML = this.built_html
		this.after_render()
	if(this.custom_after_render) this.custom_after_render.bind(this)()
}

Kato.Combo = Class.create()

Kato.Combo.prototype = Object.extend({},{

	value:null,
	min_search_chars:undefined,
	redraw_timeout:undefined,
	redraw_delay:0,
	selected_option:undefined,
	key_handled:false,
	enable_other:false,
	on_select_other:undefined,

	my_initialize:function(){
		this.min_search_chars = Kato.Extract(this.element, 'kato_min_search_chars', '')
		this.redraw_delay = Kato.Extract(this.element, 'kato_redraw_delay', '', 0)
		this.enable_other = Kato.Extract(this.element, 'kato_enable_other', false, false);
		this.on_select_other = Kato.Extract(this.element, 'kato_on_select_other', true, function() {});
	},

	build_html:function(){
		var select = $(this.id + '_select')
                if (select) {
			this.rows = this.model.data.rows
                        this.redraw_options()
		}
		if(this.value === null)this.text = ''
		this.built_html = '';
		this.built_html += this.build_header()
		this.built_html += this.build_footer()
	},

	build_header:function(){
		var o = ''
		var style = ''
		if (this.cell_binding != null && this.cell_binding[0] != undefined) {
			var form = Kato.Active[this.cell_binding[0]];
			if (form.error_mode && form.error_specs[this.cell_binding[1]] != undefined) {
				style = 'style="background-color: #FF9595;" '
			}
		}
		o += '<input onfocus="this.select()" ' + style + 'onkeydown="Kato.Active.' + this.id + '.key_down()" onkeypress="Kato.Active.' + this.id + '.key_pressed(event)" onkeyup="Kato.Active.' + this.id + '.combo_text_changed(this,event)" value="' + this.text + '" type="text" /><br />'
		//o += '<input onfocus="this.select()" onkeyup="Kato.Active.' + this.id + '.combo_text_changed(this,event)" value="' + this.text + '" type="text" /><br />'
		o += '<select size="4" style="z-index: 10; position: absolute; display: none" onchange="Kato.Active.' + this.id + '.row_clicked_combo(this.options[this.selectedIndex].value,this.options[this.selectedIndex].text)" id="' + this.id + '_select">'

		return o
	},

	handle_special_keys:function(code){

		switch (code) {

		case Event.KEY_DOWN:
			if ($(this.id + '_select').style.display == 'none') return
			if (!this.selected_option && $(this.id + '_select').firstDescendant()) {
				this.selected_option = $(this.id + '_select').firstDescendant()
			}
			else if (this.selected_option.nextSibling) {
				this.selected_option = $(this.selected_option.nextSibling)
			}
			if (this.selected_option) {
				this.selected_option.selected = true
			}
			this.key_handled = true
			break

		case Event.KEY_UP:
			if (this.selected_option && this.selected_option.previousSibling) {
				this.selected_option = $(this.selected_option.previousSibling)
				this.selected_option.selected = true
			}
			this.key_handled = true
			break

		case Event.KEY_RETURN:
			if (this.selected_option) {
				this.row_clicked_combo($(this.id + '_select').options[$(this.id + '_select').selectedIndex].value,$(this.id + '_select').options[$(this.id + '_select').selectedIndex].text)
			} else if (this.enable_other) {
				this.entered_text($(this.id + '_select').previousSibling.previousSibling.value);
			}
			this.selected_option = undefined
			this.key_handled = true
			break

		case Event.KEY_RIGHT:
		case Event.KEY_LEFT:
			this.key_handled = true
			break

		case Event.KEY_ESC:
			$(this.id + '_select').style.display='none'
			this.selected_option = undefined
			this.key_handled = true
			break
		}
	},

	key_down:function(){
		this.key_handled = false
	},

	key_pressed:function(event){
		if ($A([Event.KEY_DOWN, Event.KEY_UP]).include(event.keyCode)) {
			this.handle_special_keys(event.keyCode)
			this.key_handled = true
		}
	},

	schedule_redraw:function(params){
		if (this.redraw_timeout) {
			clearTimeout(this.redraw_timeout)
		}

		this.redraw_timeout = setTimeout((function(){
			$(this.id + '_select').style.display=''
			this.fetch_data(this.params, this.server_params)
			if (this.rows.length || this.enable_other){
				this.redraw_options()
			} else {
				$(this.id + '_select').style.display='none'
			}
		}).bind(this), this.redraw_delay)
	},

	combo_text_changed:function(input,event){
		if (this.key_handled) return
		this.handle_special_keys(event.keyCode)
		if (this.key_handled) return

		this.value = null
		this.selected_option = undefined

		this.params['regexp_' + this.columns[this.column_displayed_index].column] = input.value
		if(input.value != '' && (!this.min_search_chars || (input.value.length > this.min_search_chars))){
			this.schedule_redraw()
		} else {
			$(this.id + '_select').style.display='none'
		}
	},

        get_default_text:function(index) {
		return this.rows[index][this.column_displayed_index];
	},

	redraw_options:function(){
		var select = $(this.id + '_select')
		while(select.options.length>0){
			select.options[0]=null;
		}

		var id_on_list = false;

		for (var v=0; v < this.rows.length; v++){
			select.options[select.options.length] = new Option (this.rows[v][this.column_displayed_index],this.rows[v][0])
			if (this.rows[v][0] == this.value && this.value !== null){
				this.text = this.get_default_text(v);
				select.previousSibling.previousSibling.value = this.text;
				id_on_list = true;
			}
		}
		if (this.enable_other) {
			select.options[select.options.length] = new Option(Kato.Translate('new_combo_item'), -1);
		}
		if (!this.enable_other && !id_on_list) {
			this.text = '';
			if(this.cell_binding){
				Kato.Active[this.cell_binding[0]].set_cell(this.cell_binding[2],this.cell_binding[1],null)
			}
		}
	},

	after_render:function(){
		this.redraw_options();
	},

        entered_text:function(text) {
		var id = -1;
		for (var i = 0; i < this.rows.length; i++) {
			if (text == this.rows[i][this.column_displayed_index]) {
				id = this.rows[i][0];
			}
		}
		this.row_clicked_combo(id, text)
	},

	row_clicked_combo:function(id,text){
		this.value = id
		if (id != -1) {
			$(this.id + '_select').previousSibling.previousSibling.value = text
		} else {
			this.on_select_other($(this.id + '_select').previousSibling.previousSibling.value)
		}
		if(this.cell_binding){
			Kato.Active[this.cell_binding[0]].set_cell(this.cell_binding[2],this.cell_binding[1],id)
		}
		this.row_clicked(id)
		/* if the combo is being injected the element containing the options has been already destroyed, do not try to hide it */
		if ($(this.id + '_select')) {
			$(this.id + '_select').style.display = 'none'
		}
	},

	build_footer:function(){
		var o = '';
		o += '</select>'
		return o
	}
})
Kato.Combo.prototype = Object.extend(Kato.Combo.prototype,Kato.View.prototype)

Kato.Pivot = new Class.create()
Kato.Pivot.prototype = Object.extend({}, Kato.Grid.prototype)
Object.extend(Kato.Pivot.prototype, {
	my_initialize:function(){
		this.hierarchy = Kato.Extract(this.element,'kato_pivot_hierarchy',[])
		var renderers = Kato.Extract(this.element,'kato_pivot_renderers',{})
		this.column_widths = Kato.Extract(this.element,'kato_column_widths',{})
		this.aggregators = Kato.Extract(this.element, 'kato_pivot_aggregators', []);
		this.pivot_renderers = {}
		for (var v=0; v < this.hierarchy.length; v++){
			if(renderers[this.hierarchy[v]]){
				this.pivot_renderers[this.hierarchy[v]] = renderers[this.hierarchy[v]].bind(this)
			} else {
				this.pivot_renderers[this.hierarchy[v]] = this.build_pivot.bind(this)
			}
		}

	},

	build_html:function(){
		for (var v=0; v<this.aggregators.length; v++){
			this.aggregators[v].bind(this)()
		}
		this.built_html = '';
		this.built_html += this.build_header()
		this.built_html += this.build_rows()
		this.built_html += this.build_footer()
	},


	build_pivot:function(row,row_index,column){
		var napis = row[column] ? row[column] : '...'
		return '<tr class="kato_pivot_row kato_pivot_' +this.id + '_' + column + '"><td class="kato_pivot_row_td" colspan="' + this.number_of_columns + '">' + napis  + '</td></tr>'
	},

	row_still_mine:function(first_row, row, hierarchy_index){
		if(hierarchy_index==-1){
			return true;
		}
		var v=0
		while(v <= hierarchy_index){
			var col = this.model.to_index(this.hierarchy[v]);
			if(row[col]!=first_row[col]){

				return false
			}
			v++;
		}
		return true;
	},


	build_rows:function(hierarchy_index, row_index){
		//alert(this.build_current_hierarchy(this.rows[0],1))
		// inicjalizacja

		var o = ''

		// pierwsze wywołanie:

		if(hierarchy_index==undefined) hierarchy_index = -1
		if(row_index==undefined) row_index = 0

		//alert([hierarchy_index, row_index])

		// wyszło poza hierarchię, czyli koniec rekursji: rysuje pojedynczy wiersz

		//alert('ewaluuje: ' + [hierarchy_index,this.hierarchy.length])
		if(hierarchy_index>=this.hierarchy.length){
			if (this.rows[row_index])
			o += this.build_row(this.rows[row_index],row_index)
			return ([o, row_index+1])
		}

		// jesteśmy w obrębie hierarchii

		var hierarchy_column = this.model.to_index(this.hierarchy[hierarchy_index])
		var first_row = $A(this.rows[row_index]).slice()

		// pętla
		if(this.pivot_renderers[this.hierarchy[hierarchy_index]])o += this.pivot_renderers[this.hierarchy[hierarchy_index]](this.rows[row_index],row_index,hierarchy_column)

		//o += '<tr><td colspan="11">' + this.hierarchy[hierarchy_index] + '=' + this.rows[row_index][hierarchy_column] + '</td></tr>'

		while(
			(this.rows[row_index])
			&& this.row_still_mine(first_row,this.rows[row_index],hierarchy_index)
		) {

			var result = this.build_rows(hierarchy_index+1, row_index)
			row_index = result[1]
			o += result[0]
		}
		if(hierarchy_index==-1) return o
		return [o, row_index];
	}

/*
	build_cell:function(row,index){
		var o = ''
		o += '<td>' + row[index] + '</td>'
		return o
	}
*/
})


Kato.Stub = new Class.create()
Kato.Stub.prototype = Object.extend(Kato.Stub.prototype,{
	my_initialize:function(){
	},

	build_html:function(){
		this.built_html = '<h3>To jest pieniek</h3>'
	}
})
Kato.Stub.prototype = Object.extend(Kato.Stub.prototype,Kato.View.prototype)

Kato.SpreadSheet = new Class.create()
Kato.SpreadSheet.prototype = Object.extend(Kato.SpreadSheet.prototype,{
    /*
     * kato_connect_to - object (View) to connect to
     * kato_format - list of export formats (spreadsheet,csv,html,xls)
     */
	connect_to: undefined,
        format: undefined,

	my_initialize:function(){
		this.connect_to = Kato.Extract(this.element,'kato_connect_to','')
                if (this.element.hasAttribute('kato_format')){
			this.format = eval('([' + this.element.getAttribute('kato_format') + '])')
		} else {
                    this.format = [ 'spreadsheet' ]
                }
	},

	get_href:function(f){
		var connect_view = Kato.Active[this.connect_to].model.view
		var connect_params = Kato.Active[this.connect_to].params

		/* Remove limit and offset from connect_params - we want to see oll pages*/
                connect_params.limit = undefined
                connect_params.offset = undefined

		var href = "?kato_magic="
		href +=	$H(Kato.Active[this.connect_to].model.provider.get_postBody(connect_view,connect_params,{ format: f, name: this.connect_to })).toJSON()
		return href
	},

        get_link:function(f){
                var o = '<div class="kato_'+f+'"><a href="" onclick="this.href = Kato.Active.'+this.id+'.get_href(\''+f+'\')" ><span>'
		o += Kato.Translate("kato_"+f)+'<span></a></div>'
                return o
        },

	build_html:function(){
            this.built_html = '<table><tr>'
            for (var i=0; i< this.format.length; i++) {
               this.built_html += '<td>'+this.get_link(this.format[i])+'</td>'
            }
            this.built_html += '</tr></table>'
	}
})
Kato.SpreadSheet.prototype = Object.extend(Kato.SpreadSheet.prototype,Kato.View.prototype)

Kato.Matrix = new Class.create()
Kato.Matrix.prototype = Object.extend(Kato.Matrix.prototype,{
	column_x: undefined,
	column_y: undefined,
	x_map: undefined,
	y_map: undefined,
	full_map: undefined,

	my_initialize:function(){
		this.column_x = Kato.Extract(this.element,'kato_column_x','')
		this.column_y = Kato.Extract(this.element,'kato_column_y','')
		this.x_map = []
		this.y_map = []
		this.full_map = {}
 	},

	build_html:function(){
		this.build_maps()
		this.built_html = '';
		this.built_html += this.build_header()
		this.built_html += this.build_rows()
		this.built_html += this.build_footer()
	},

        build_resizeable_cell:function(){
            var o = '<span onmousedown="Kato.Resize(this.parentNode,Event.pointerX(event)); Event.stop(event)" '
            o += 'style="position: relative; float: right; height: 100%; width: 10px; cursor:e-resize">&nbsp;</span>'
            return o
        },

	build_header:function(){
		var o = '<div style="overflow: auto"><table class="kato kato_table">\n';
		if(this.has_block('matrix_header')){
			var ths = '<tr><th>'
                        ths += this.build_resizeable_cell()
                        ths += '&nbsp;</th>'
			for (var v = 0; v < this.x_map.length; v++) {
				ths += '<th>';
                                ths += this.build_resizeable_cell()
                                ths += Kato.Translate(this.x_map[v]);
				ths += '</th>';
				this.number_of_columns++
			}
			o += ths
			o += '</tr>'
		}
		return o;
	},

	build_maps:function(){
		this.full_map = {}
		var x_map_o = {}
		var y_map_o = {}
		this.x_map = []
		this.y_map = []
		var i_x = this.model.col_name_to_index(this.column_x)
		var i_y = this.model.col_name_to_index(this.column_y)

		for(var v=0; v< this.rows.length; v++){
			var klucz_x = this.rows[v][i_x]
			var klucz_y = this.rows[v][i_y]

			if(this.full_map[klucz_y]===undefined){
				this.full_map[klucz_y] = {}
			}

			if(this.full_map[klucz_y][klucz_x] === undefined){
				this.full_map[klucz_y][klucz_x] = []
			}

			this.full_map[klucz_y][klucz_x].push(v)

			x_map_o[klucz_x] = true
			y_map_o[klucz_y] = true
		}

		for(var key in x_map_o) {
			this.x_map.push(key)
		}
		for(var key in y_map_o) {
			this.y_map.push(key)
		}
	},

	build_rows:function(){
		var o = ''
		for (var v = 0; v < this.y_map.length; v++) {
			o += this.build_row(this.y_map[v])
		}
		return o;
	},

	build_row:function(y_value){
		var o = ''
		o += "<tr>\n"
		if(this.has_block('matrix_header')){
			o += '<th>';
			o += Kato.Translate(y_value);
			o += '</th>';
		}
		for (var v = 0; v < this.x_map.length; v++) {
			var val = this.full_map[y_value][this.x_map[v]][0]
			var row = this.rows[val]
			o += this.cell_renderers[this.column_displayed_index](row, this.column_displayed_index, this.id, val)
		}
		o += "</tr>\n"
		return o
	},

	build_footer:function(){
		var o = ''
		o += '</table></div>'
		return o;
	},

        before_render:function(){
		this.save_th_widths()
	},

	after_render:function(){
		this.set_th_widths()
	},

	save_th_widths:function(){
		this.saved_th_widths = []
		var ths = this.element.getElementsByTagName('th')
		for (var v=0; v < ths.length; v++){
			this.saved_th_widths[v] = ths[v].style.width
		}
	},

	set_th_widths:function(){
		var ths = this.element.getElementsByTagName('th')
		if (!this.saved_th_widths){
			for (var v=0; v < ths.length; v++){
				ths[v].style.width = ths[v].style.width
			}
		} else {
			for (var v=0; v < ths.length; v++){
				if(this.saved_th_widths[v]) {
					ths[v].style.width = this.saved_th_widths[v]
				}
			}

		}
	}

})
Kato.Matrix.prototype = Object.extend(Kato.Matrix.prototype,Kato.View.prototype)

Kato.GooglePie = new Class.create()
Kato.GooglePie.prototype = Object.extend(Kato.GooglePie.prototype,{

	label_column: undefined,
	value_column: undefined,
	chart_size: undefined,

	my_initialize:function(){
		this.label_column = (Kato.Extract(this.element,'kato_label_column',''))
		this.value_column = (Kato.Extract(this.element,'kato_value_column',''))
		this.chart_size = (Kato.Extract(this.element,'kato_chart_size',''))
		if (!this.chart_size){
			this.chart_size = "500x200"
		}
	},

	build_html:function(){
		var values = []
		var labels = []

		var i_label_column = this.model.col_name_to_index(this.label_column)
		var i_value_column = this.model.col_name_to_index(this.value_column)

		var max = 0;

		for (var v = 0; v < this.rows.length; v++){
			val = parseFloat(this.rows[v][i_value_column]);
			values.push(val)
			labels.push(this.rows[v][i_label_column])

			if (val > max) {
				max = val;
			}
		}

		values = values.join(',')
		labels = labels.join('|')

		this.built_html = '<img alt="wykres" src="http://chart.apis.google.com/chart?cht=p3&chd=t:' + values + '&chs=' + this.chart_size + '&chl=' + labels + '&chds=0,' + max + '" />'
	}
})
Kato.GooglePie.prototype = Object.extend(Kato.GooglePie.prototype,Kato.View.prototype)




Kato.SpeedForm = new Class.create()
Kato.SpeedForm.prototype = Object.extend(Kato.SpeedForm.prototype, Kato.View.prototype)
Kato.SpeedForm.prototype = Object.extend(Kato.SpeedForm.prototype,{

	my_initialize:function(){
		this.string_from_row=Kato.Extract(this.element,'kato_string_from_row',[])
		this.row_from_string=Kato.Extract(this.element,'kato_row_from_string',[])
		this.afterfetch_handlers.push(this.update_text)
	},

	update_text:function(){
		if(this.rows.length>0){
			this.text = this.string_from_row[0].bind(this)(this.rows[0])
		} else {
			this.text = ''
		}
	},

	keyup:function(text){
		this.text = text;
		this.row_from_string[0].bind(this)(text)
	},

	build_html:function(){
		var o = '<div class="kato_speedform">'
		o += '<input style="width: 100%" onkeyup="var i=Kato.Active.' + this.id + '; i.keyup(this.value); if(event.keyCode==Event.KEY_RETURN){i.send_data()}" value="' + this.text + '" />'
		o += '<br /><input type="button" onclick="Kato.Active.' + this.id + '.send_data()" value="' + ((this.rows[0][0]!=null) ? Kato.Translate('edit') : Kato.Translate('add')) + '"></div>'
		this.built_html = o
	}
})


Kato.AggregatingTemplate = new Class.create()
Kato.AggregatingTemplate.prototype = Object.extend(Kato.AggregatingTemplate.prototype,{
	template_content: undefined,
	model_name: undefined,
	aggregators: undefined,

	my_initialize:function(){
		this.template_content = this.element.innerHTML;
		if(this.element.hasAttribute('kato_model_alias')){
			this.model_name = this.element.getAttribute('kato_model_alias')
		}
		this.aggregators = Kato.Extract(this.element, 'kato_aggregators', []);

	},

	build_html:function(){
		var o = ''
		var aggregates = {}
		var meta = {}
		for (var v = 0; v < this.rows.length; v++){
			for (var r = 0; r < this.aggregators.length; r++){
				this.aggregators[r].bind(this)(this.rows[v], aggregates)
			}
		}
		o += this.substitute(this.template_content, null ,meta, aggregates)
		this.built_html = o
	},

	substitute:function(content, row, meta, aggr){

		var nice_row = null
		if (row) {
			nice_row = this.model.hash_from_row(row)
		}
		eval('var ' + (this.model_name ? this.model_name : this.model.view) + '= nice_row' )
		var substitute
		while((substitute = content.match(/\${([^}]*)}/)))
		{
			content = content.replace(/\${([^}]*)}/,eval(substitute[1]))
		}
		while((substitute = content.match(/\$%7b(.+?)%7d/i)))
		{
			content = content.replace(/\$%7b(.+?)%7d/i,eval(substitute[1]))
		}
		return content
	}

})
Kato.AggregatingTemplate.prototype = Object.extend(Kato.AggregatingTemplate.prototype,Kato.View.prototype)




Kato.Template = new Class.create()
Kato.Template.prototype = Object.extend(Kato.Template.prototype,{
	template_content: undefined,
	model_name: undefined,

	my_initialize:function(){
		this.template_content = this.element.innerHTML;
		if(this.element.hasAttribute('kato_model_alias')){
			this.model_name = this.element.getAttribute('kato_model_alias')
		}
	},

	build_html:function(){
		var o = ''
		var meta = {}
		meta.count = this.rows.length
		meta.first = true
		for (var v = 0; v < this.rows.length; v++){
			if(v==this.rows.length-1) meta.last = true
			meta.number = v + 1
			meta.row_number = v
			o += this.substitute(this.template_content, this.rows[v],meta)
			meta.first = false
		}
		this.built_html = o
	},

	substitute:function(content, row, meta){
		var nice_row = this.model.hash_from_row(row)
		eval('var ' + (this.model_name ? this.model_name : this.model.view) + '= nice_row' )
		var substitute
		while((substitute = content.match(/\${([^}]*)}/)))
		{
			content = content.replace(/\${([^}]*)}/,eval(substitute[1]))
		}
		while((substitute = content.match(/\$%7b(.+?)%7d/i)))
		{
			content = content.replace(/\$%7b(.+?)%7d/i,eval(substitute[1]))
		}
		return content
	}

})
Kato.Template.prototype = Object.extend(Kato.Template.prototype,Kato.View.prototype)


Kato.SortableTemplate = Class.create(Kato.Template, {
	custom_after_render: function() {
		var update_function = function () {
			var col_to_update = this.model.to_index(this.ordering_column);
			if (!col_to_update)
				return;
			var children = $(this.element.id + 'Ksortable').childElements();
			for (var i=0; i < children.length; i++) {
				var e = children[i];
				var row_number = parseInt(e.getAttribute('kato_row_number'));
				this.set_cell(row_number, col_to_update, String(i)); // FIXME: i + offset?
			}
			this.send_data();
		}
		Sortable.create(this.element.id+'Ksortable', {
			onUpdate: update_function.bind(this),
			tag: this.tag
		});
	},

	my_initialize: function($super) {
		$super();
		this.ordering_column = Kato.Extract(this.element, 'kato_ordering_column', '');
		if (this.ordering_column == '') {
			this.ordering_column = 'ordering';
		}
		this.tag = Kato.Extract(this.element, 'kato_sortable_tag', '');
		if (this.tag == '') {
			this.tag = 'div';
		}
		this.params_template = {};
		this.params_template['orderby_'+this.ordering_column] = 'asc';
	},

	build_html: function($super) {
		$super();
		this.built_html = '<div id="' + this.element.id + 'Ksortable" class="sortable_template">' + this.built_html + '</div>';
	},

	substitute: function($super, content, row, meta) {
		return '<' + this.tag + ' id="' + this.element.id + 'Ksortable_' + meta.row_number + '" kato_row_number="' + meta.row_number + '">' + $super(content, row, meta) + '</' + this.tag + '>';
	}
});

Kato.Tree = new Class.create()
Kato.Tree.prototype = Object.extend({}, Kato.View.prototype)
Object.extend(Kato.Tree.prototype,{

	toggled_rows:	undefined,
	parent:			undefined,
	collapsed_rows:	undefined,
	default_collapsed:	true,

	my_initialize:function(){
		this.parent = this.element.hasAttribute('kato_tree_parent') ? this.element.getAttribute('kato_tree_parent') : 'null'
		this.parent_column = (this.element.getAttribute('kato_tree_parent_column'))
		this.collapsed_rows = {}
	},

	build_html:function(){
		this.make_tree()
		var o = ''
		o += this.build_header();
		o += this.build_rows();
		o += this.build_footer();
		this.built_html = o
	},

	tree_toggle:function(pk){
		this.collapsed_rows[pk] = !this.collapsed_rows[pk]
		this.render()
	},

	is_collapsed:function(pk){
		if(this.collapsed_rows[pk]==undefined){
			this.collapsed_rows[pk] = this.default_collapsed
		}
		return this.collapsed_rows[pk]
	},


	build_cell:function(row,index){
		return row[index]
	},


	build_node:function(row){
		var o = ''
		var children = []
		var span_class = ''
		if(row != this.parent){
			children = this.tree[(row[0])]
			o += '<li>'
			if(children){
				if(this.is_collapsed(row[0]) && row != this.parent){
					span_class = 'kato_tree_collapsed'
				} else {
					span_class = 'kato_tree_not_collapsed'
				}
			}
			if(children){
				o += '<span class="' + span_class + '" style="cursor: pointer" onclick="Kato.Active.' + this.id + '.tree_toggle(\'' + row[0] + '\');">&nbsp;&nbsp;&nbsp;&nbsp;</span> '
			} else {
				o += '<span class="' + span_class + '"></span> '
			}
			o += '<a href="#stub" onclick="Kato.Active.' + this.id + '.row_clicked(\'' + row[0] + '\'); return false">'
			o += this.cell_renderers[this.column_displayed_index](row,this.column_displayed_index)  + '</a></li>'
		} else {
			children = this.tree[this.parent]
		}
		if(children){
			if(this.is_collapsed(row[0]) && row != this.parent){
				o += '<li style="display: none">'
			} else {
				o += '<li>'
			}
			o += '<ul>'
			for (var v=0; v < children.length; v++){
				o += this.build_node(children[v])
			}
			o += '</ul></li>'
		}
		return o
	},

	build_rows:function() {
		var o = ''
		o += '<ul class="kato_tree">'
		o += this.build_node(this.parent)
		o += '</ul>'
		return o
	},

	make_tree:function() {
		var parent_column_index = this.model.to_index(this.parent_column)
		this.parent_column_index = parent_column_index
		this.tree = {}
		for (var v = 0; v < this.rows.length; v++){

//			if(this.rows[v][parent_column_index]){
				if(this.tree[this.rows[v][parent_column_index]]){
					this.tree[this.rows[v][parent_column_index]].push(this.rows[v])
				} else {
					this.tree[this.rows[v][parent_column_index]] = [this.rows[v]]
				}
//			} else {
//				this.tree.root.push(this.rows[v])
//			}

		}
	},

	build_footer:function() { return '' },
	build_controls:function() { return '' },
	build_header:function() { return '' }


})




Kato.Label = Class.create()
Kato.Label.prototype = Object.extend({}, Kato.Detail.prototype)
Object.extend(Kato.Label.prototype, {

	build_rows:function() {
		var o = ''
			for (var v = 0; v < this.columns.length; v++) {
				var vm = this.column_order_map[v]
				if (this.cell_renderers[vm]) {
					o += this.build_row(this.rows[0], vm)
					break
				}
			}
		return o
	},

	build_row:function(row, index) {
		return this.cell_renderers[index](row, index, this.id, 0)
	},

	build_footer:function() { return '' },
	build_controls:function() { return '' },
	build_header:function() { return '' }
})

Kato.Calendar = new Class.create()
Kato.Calendar.prototype = Object.extend({},Kato.View.prototype)
Object.extend(Kato.Calendar.prototype,{
	calendar_from: undefined,
	calendar_to: undefined,

	my_initialize:function(){
		if(this.element.hasAttribute('kato_timestamp_column') && !this.timestamp_column){
			this.timestamp_column = this.element.getAttribute('kato_timestamp_column')
		}
		this.onchange_date_handlers = Kato.Extract(this.element,'kato_onchange_date_handlers',[])
		this.events_renderer = Kato.Extract(this.element,'kato_events_renderer',[])
	},

	build_html:function(){
		if(this.params['ge_' + this.timestamp_column]){
			this.calendar_from = Kato.Utils.date_from_string(this.params['ge_' + this.timestamp_column])
		}
		if(this.params['lt_' + this.timestamp_column]){
			this.calendar_to = Kato.Utils.date_from_string(this.params['lt_' + this.timestamp_column])
		}
		this.built_html = ''
		this.built_html += this.render_header()
		this.built_html += this.render_days()
		this.built_html += this.render_footer()
	},

	render_header:function(){
		var o=''
		o += '<table class="kato kato_calendar">'
		return o
	},

	render_footer:function(date){
		var o = ''
		o += '</table>'
		return o
	},

	render_days:function(){
		var month = -1
		var o = ''
		var date = this.calendar_from ? new Date(this.calendar_from) : new Date()
		date.setDate(1)
		var max_date =  this.calendar_to ? new Date(this.calendar_to) : this.max_date
		while(date < max_date){
			if(date.getMonth()!=month){
				if(month!=-1) o += this.pad_to_end(date)
				month = date.getMonth()
				o += this.render_month_start(date)
			}
			o += this.render_day(date)
			date.setDate(date.getDate() + 1)
			if(date.getDay()==1) o += this.render_week_end(date)
		}
		o += this.pad_to_end(date)
		return o
	},

	month_forward:function(){
		this.calendar_from.setMonth(this.calendar_from.getMonth()+1)
		this.calendar_to.setMonth(this.calendar_to.getMonth()+1)
		this.rerender_with_changed_range()
	},

	month_back:function(){
		this.calendar_from.setMonth(this.calendar_from.getMonth()-1)
		this.calendar_to.setMonth(this.calendar_to.getMonth()-1)
		this.rerender_with_changed_range()
	},

	year_forward:function() {
		this.calendar_from.setFullYear(this.calendar_from.getFullYear()+1)
		this.calendar_to.setFullYear(this.calendar_to.getFullYear()+1)
		this.rerender_with_changed_range()
	},

	year_back:function() {
		this.calendar_from.setFullYear(this.calendar_from.getFullYear()-1)
		this.calendar_to.setFullYear(this.calendar_to.getFullYear()-1)
		this.rerender_with_changed_range()
	},

	rerender_with_changed_range:function() {
		this.params['ge_' + this.timestamp_column]=Kato.Utils.string_from_date(this.calendar_from)
		this.params['lt_' + this.timestamp_column]=Kato.Utils.string_from_date(this.calendar_to)
		this.render()
	},

	render_month_start:function(date){
		var o = ''
		o += '<tr><th colspan="7" class="month_name">'
		if (this.has_block('calendar_month_navigation')){
			o +=  '<span style="float: right">'
			o += '<a href="#" onclick="Kato.Active.' + this.id + '.year_back()">&laquo;</a> '
			o += '<a href="#" onclick="Kato.Active.' + this.id + '.month_back()">&lt;</a> '
			o += '<a href="#" onclick="Kato.Active.' + this.id + '.month_forward()">&gt;</a> '
			o += '<a href="#" onclick="Kato.Active.' + this.id + '.year_forward()">&raquo;</a>'
			o += '</span>'
		}
		o += date.getFullYear() + ' ' + Kato.Months[date.getMonth()] + '</th></tr>\n'
		o += '<tr><th style="width: 14%">'+Kato.Weekdays[0]+'</th><th style="width: 14%">'+Kato.Weekdays[1]
		o += '</th><th style="width: 14%">'+Kato.Weekdays[2]+'</th><th style="width: 14%">'+Kato.Weekdays[3]
		o += '</th><th style="width: 14%">'+Kato.Weekdays[4]+'</th><th style="width: 14%">'+Kato.Weekdays[5]
		o += '</th><th style="width: 14%">'+Kato.Weekdays[6]+'</th><tr>\n'

		var padding = date.getDay()-1
		if (padding<0)padding += 7
		while (padding--){
			o += '<td>&nbsp;</td>'
		}
		return o
	},

	pad_to_end:function(date){
		var o = ''
		var padding = 8-date.getDay()
		if(padding>7)padding -= 7
		while(padding--){
			o += '<td>&nbsp;</td>'
		}
		o += '</tr>'
		return o
	},

	render_week_end:function(date){
		var o = ''
		o += '</tr><tr>'
		return o
	},

	after_fetch_data:function(){
		if(this.timestamp_column_index===undefined){
			var timestamp_column_index = this.timestamp_column_index?this.timestamp_column_index:0
			if(!this.timestamp_column_index){
				for(var v = 0; v < this.columns.length; v++){
					if(this.columns[v].type.indexOf('timestamp')===0 || this.columns[v].type.indexOf('date')===0){
						this.timestamp_column = this.columns[v].column
						this.timestamp_column_index = v
						timestamp_column_index = v
						this.timestamp_column_index = v
					}
				}
			}
		}

		this.calendar_hash = {}
		var min_date = '9'
		var max_date = '0'
		for (var v=0; v < this.rows.length; v++){
			if(!this.rows[v][this.timestamp_column_index]) continue;
			var date = (this.rows[v][this.timestamp_column_index].substr(0,10))
			if(date>max_date) max_date = date
			if(date<min_date) min_date = date
			if(!this.calendar_hash[date]){
				this.calendar_hash[date] = [this.rows[v]]
			} else {
				this.calendar_hash[date].push(this.rows[v])
			}
		}
		this.min_date = Kato.Utils.date_from_string(min_date)
		this.max_date = Kato.Utils.date_from_string(max_date)
	},

	change_date:function(date_string){
		for (var v=0; v < this.onchange_date_handlers.length; v++){
			this.onchange_date_handlers[v].bind(this)(date_string)
		}
	},

	get_day_element:function(date_string){
		return this.id + date_string;
	},

	render_day:function(date){
		var o = ''
		date = this.string_from_date(date)
		var events = this.calendar_hash[date]
		if(!events) events = []
		o += '<td><div class="date">'
		if(this.onchange_date_handlers.length>0){
			o += ('<a href="#" onclick="Kato.Active.' + this.id + '.change_date(\'' + date.substr(0,10) + '\'); return false">')
			o += date.substr(8,2)
			o += '</a>'
		} else {
			o += date.substr(8,2)
		}
		o += '</div><div id="' + this.get_day_element(date) + '">'
		if (this.events_renderer[0] != undefined) {
			o += this.events_renderer[0](events)
		} else {
			o += '<ul >'
			for(var v=0; v < events.length; v++){
				o += this.render_event(events[v])
			}
			o += '</ul>'
		}
		o += '</div></td>'
		return o
	},

	render_event:function(event){
		return '<li><a href="#" onclick="Kato.Active.' + this.id+ '.row_clicked(\'' + event[0] + '\'); return false">' +
		this.cell_renderers[this.column_displayed_index].bind(this)(event,this.column_displayed_index) + '</a></li>'
	},

	string_from_date:function(date) {
		var month = '' + (date.getMonth()+1)
		if(month.length==1) month = '0' + month
		var day = '' + (date.getDate())
		if(day.length==1) day = '0' + day
		return date.getFullYear() + '-' + (month) + '-' + (day)
	},

	build_cell:function(row,index){
		return row[index]
	}
})


Kato.Init = function(options){
	Kato.Utils.idle.busy()
	if (options === undefined) options = {}
	Object.extend(Kato.Config, options)
	if(Kato.Config.GetRelations){
		Kato.PrimaryKeys = Kato.Config.Provider.get_primary_keys()
	} else {
		Kato.PrimaryKeys = {}
	}
	Object.extend(Kato.PrimaryKeys, Kato.CustomPrimaryKeys)

	var qs = window.location.search.slice(1)
	if (qs != '') {
		var chunks = qs.split('&')
		for (var i = 0; i < chunks.length; i++) {
			var parts = chunks[i].split('=')
			var key = parts[0]
			var val = parts[1] ? parts[1] : ''
			if (key.search(/\[\]$/) == -1) {
				Kato.Request[key] = decodeURIComponent(val)
			}
			else {
				var realkey = key.replace(/\[\]$/, '')
				if (Kato.Request[realkey] === undefined) {
					Kato.Request[realkey] = []
				}
				Kato.Request[realkey].push(decodeURIComponent(val))
			}
		}
	}

	Kato.Pathname = window.location.pathname

	var divs_real = document.body.getElementsByTagName('div');
	var divs = []
	for (var f=0; f < divs_real.length; f++){
		divs.push(divs_real[f])
	}
	for(var f=0; f < divs.length; f++){
		if(!$(divs[f]).hasAttribute('kato_type'))continue;
		Kato.Create(divs[f])
		Kato.Active[divs[f].id].render()
	}
	if(Kato.CustomInit)Kato.CustomInit()
	Kato.Updater()
	Kato.Utils.idle.idle()
}

Kato.BuildFromScratch = function(params,parent){
	var element = document.createElement('div')
	element.setAttribute('id',params['id'])

	if(Kato.Templates[element.id]){
		for (var key in Kato.Templates[element.id]){
			if(key!='id'){
				element.setAttribute('kato_' + key, Kato.Templates[element.id][key])
			}
		}
	}

	for (var regex in Kato.TemplateRegexes) {
		var r = new RegExp(regex)
		if (r.match(element.id)) {
			for (var key in Kato.TemplateRegexes[regex]){
				if(key!='id'){
					element.setAttribute('kato_' + key, Kato.TemplateRegexes[regex][key])
				}
			}
		}
	}

	for (var key in params){
		if(key!='id'){
			element.setAttribute('kato_' + key, params[key])
		}
	}

	parent.innerHTML = ''
	parent.appendChild(element)
	if(Kato.Active[params.id]){			// widget already exists
		Kato.Active[params.id].element = $(element)
	} else {
		Kato.Create(element)				// build widget
	}
}

Kato.Create = function(element){
	element = $(element);
	if (!element.hasAttribute('kato_type')) return;
	var init = eval(element.getAttribute('kato_type'))
	var construct=new init(element)
	if(Kato.Active[element.id]){
		Kato.Error('[error] id '+construct.id+' already taken')
		return
	}
	Kato.Active[element.id] = construct
	try {
		Kato.Build(construct)
	} catch (e){
		Kato.Error(e)
	}
}

Kato.Build = function(construct){
	construct.build()
}

Kato.Debugger = Class.create()
Kato.Debugger.prototype = Object.extend(Kato.Debugger.prototype,{

	build:function(){
		this.object_debugged = '---'
		this.render()
	},

	render:function(){
		var o = ''
		o += '<input type="button" class="kato_button" value="refresh" onclick="Kato.Active.' + this.id + '.render()"/>'

		o += '<select onchange="Kato.Active.' + this.id + '.debug(this.options[this.selectedIndex].value)">\n'
		for (el in Kato.Active){
			if (el==this.object_debugged){
				o += '<option selected="selected">'
			} else {
				o += '<option>'
			}
			o += el + '</option>\n'
		}
		o += '</select> | '
		o += this.object_debugged
		o += '<hr />'
		o += this.tools()
		this.element.innerHTML = o
	},

	tools:function(){
		var o = ''
		o += "<input type='button' class='kato_button' value='view data' onclick='Kato.Debugger.DumpRows(Kato.Active." + this.object_debugged + ".rows)' />"
		o += "<input type='button' class='kato_button' value='model data' onclick='Kato.Debugger.DumpRows(Kato.Active." + this.object_debugged + ".data.rows)' />"
		o += "<input type='button' class='kato_button' value='params' onclick='DumperAlert(Kato.Active." + this.object_debugged + ".params)' />"
		o += "<input type='button' class='kato_button' value='renderers' onclick='DumperAlert(Kato.Active." + this.object_debugged + ".cell_renderers)' />"
		return o
	},

	debug:function(name){
		this.object_debugged = name
		this.render()
	}
})

Kato.Debugger.DumpRows = function (rows){
	var o = ''
	for (var v = 0 ; v < rows.length; v++){
		o += rows[v] + '\n'
	}
	alert(o)
}

Kato.Utils.nl2br = function (text){
	if(!text) return ''
	var lines = text.split("\n")

        text = ''

	for (var v=0; v < lines.length; v++){
		line = lines[v]
		line = line.replace(/(.*)/, '<p>$1</p>')
		text += line
	}

        return text;

}

Kato.Store.EditorSem ={
	in_use:			false,
	check_out:		function(){
						if (this.in_use){
							alert(Kato.Translate('save_last_item_first'))
							return false
						}
						this.in_use = false
						this.in_use = true
						return true
					},

	check_in:		function(){
						this.in_use = false
					}
}

Kato.TextField = new Class.create()
Kato.TextField.prototype = Object.extend(Kato.TextField.prototype,{
	featured_column: undefined,

	my_initialize:function(){
		this.params_template = {
			limit: 1, offset: 0
		}
		this.featured_column = Kato.Extract(this.element,'kato_featured_column','')
	},

	build_html:function(){
		var row = this.rows[0]
		var featured_column_index = this.model.col_name_to_index(this.featured_column)

		var o = '';
		var val = row[featured_column_index]
		val = ((val!=null)?val:'')
		o += '<div class="form_editable" onclick="if(Kato.Store.EditorSem.check_out()){this.style.display=\'none\'; this.nextSibling.style.display=\'\'; this.nextSibling.childNodes[0].focus()}">' + Kato.Utils.escape_quotes(val) + '&nbsp;</div><div style="display: none">'
		o += '<input onclick="Event.stop(event)" onkeyup="Kato.Active.'
				+ this.id
				+ '.set_cell(' + 0 + ',' +  featured_column_index + ',this.value)" class="text" type="text" value="' + val + '" />'
		o += '<input type="button" onclick="Kato.Store.EditorSem.check_in(); Kato.Active.'
				+ this.id
				+ '.set_cell(' + 0 + ',' +  featured_column_index + ', previousSibling.value); Kato.Active.' + this.id + '.send_data()" value="' + Kato.Translate('edit') + '">'
		o += '</div>'
		this.built_html = o
	}


})
Kato.TextField.prototype = Object.extend(Kato.TextField.prototype,Kato.View.prototype)

Kato.DropdownField = Class.create(Kato.View, {
	my_initialize:function(){
		this.params_template = {
			limit: 1, offset: 0
		}
		this.featured_column = Kato.Extract(this.element,'kato_featured_column','')
		this.referenced_model = Kato.Extract(this.element,'kato_referenced_model','')
		this.display_id_column = Kato.Extract(this.element,'kato_display_id_column', false, false)
	},

	build_html:function(){
		o = ''
		o += '<span id="' +this.id + 'Kfield"></span/>'
		this.built_html = o
	},
	after_render:function(){
		var featured_column_index = this.model.col_name_to_index(this.featured_column);
		var params = {};
		var id = this.id + 'Kfield';
		params.id = id;
		if (this.referenced_model) {
			params.model = this.referenced_model;
		} else {
			params.model = Kato.PrimaryKeys[this.featured_column];
		}
		if (!params.model) {
			Kato.Error("can't determine model and none supplied (" + this.id +")");
		}
		params.type = 'Kato.Dropdown';
		params.cell_binding= "'" + this.id + "'," + featured_column_index;
		if (this.display_id_column) {
			params.visible_columns='0';
		}
		Kato.BuildFromScratch(params,$(id))
		Kato.Active[params.id].parent_view_id = this.id
		var host = this
		Kato.Active[params.id].change_handlers = [
			function(ja, id, row_index){
				for (var v=0; v<host.change_handlers.length; v++){
					host.change_handlers[v](host,id,row_index)
				}
			},
			function(ja, id, row_index){
				Kato.Active[ja.parent_view_id].send_data();
			},
			Kato.Store.EditorSem.check_in
		]
		if(this.custom_after_render) this.custom_after_render.bind(this)()
		Kato.Active[id].value = this.rows[0][featured_column_index]
		Kato.Active[id].render()
		this.errors_list = this.element.getElementsByTagName('ul')[0]
	}
})


Kato.TextArea = new Class.create()
Kato.TextArea.prototype = Object.extend(Kato.TextArea.prototype,{
	featured_column: undefined,

	my_initialize:function(){
		this.params_template = {
			limit: 1, offset: 0
		}
		this.featured_column = Kato.Extract(this.element,'kato_featured_column','')
	},

	build_html:function(){
		var row = this.rows[0]
		var featured_column_index = this.model.col_name_to_index(this.featured_column)

		var o = '';
		var val = row[featured_column_index]
		val = ((val!=null)?val:'')

		o += '<div class="form_editable" onclick="if(Kato.Store.EditorSem.check_out()){this.style.display=\'none\'; this.nextSibling.style.display=\'\'; this.nextSibling.childNodes[0].focus()}">' + Kato.Utils.textile(val) + '&nbsp;<br/></div><div style="display: none">'
		o += '<textarea onclick="Event.stop(event)" onkeyup="Kato.Active.'
				+ this.id
				+ '.set_cell(' + 0 + ',' +  featured_column_index + ',this.value)" class="text">'
				+ Kato.Utils.escape_tags(val) + '</textarea>'

		o += '<input type="button" onclick="Kato.Store.EditorSem.check_in(); Kato.Active.'
				+ this.id
				+ '.set_cell(' + 0 + ',' +  featured_column_index + ', previousSibling.value); Kato.Active.' + this.id + '.send_data()" value="' + Kato.Translate('edit') + '">'
		o += '</div>'
		this.built_html = o
	}
})
Kato.TextArea.prototype = Object.extend(Kato.TextArea.prototype, Kato.View.prototype)

Kato.Utils.textile = function (text){
	if(typeof(text)!='string') return text
	text = text.replace (/\*(.+?)\*/g,'<strong>$1</strong>')
	text = text.replace (/\b_(.+?)_\b/g,'<em>$1</em>')
	text = text.replace (/\b__(.+?)__\b/g,'<i>$1</i>')
	text = text.replace (/\b\*\*(.+?)\*\*\b/g,'<b>$1</b>')
	text = text.replace (/"(.+?)":(https?:\/\/[\w./?&;]*)/g,'<a href="$2">$1</a>')

	var lines = text.split(/\r|\n/)
	text = ''
	for (var v=0; v < lines.length; v++){
		line = lines[v]
		if (line.match(/^\s*[ph]\.\s+(.*)/)) {
			line = line.replace(/^\s*p\.\s+(.*)/, '<p>$1</p>')
			line = line.replace(/^\s*h(\d)\.\s+(.*)/, '<h$1>$2</h$1>')
		} else {
			line = '<p>' + line + '</p>'
		}
		text += line
	}
	return text
}

Kato.Utils.copy_to_field = function(frm, field) {
	return function(text) {
		Kato.Active[frm].set_form_cell(field, text, false);
	}
}

Kato.Utils.is_array = function(obj) {
	try {
		return obj.constructor == Array
	} catch (err) {
		return false
	}
}

Kato.Utils.escape_quotes = function(string) {
	if(typeof(string)!='string') return string
	return string.replace(/"/g, "&quot;")
}

Kato.Utils.escape_tags = function(string) {
	if(typeof(string)!='string') return string
	return string.replace(/</g, "&lt;").replace(/>/g, "&gt;")
}

Kato.Utils.escape_all = function(string) {
        if(typeof(string)!='string') {
                string = String(string);
        }
        return string.replace(/&/g, '&amp;').replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}

/*
Local variables:
show-trailing-whitespace: nil
indent-tabs-mode: t
js2-basic-offset: 8
End:
*/
